Lazy loading and using Thread.MemoryBarrier

When designing a class that refers to another object, it may turn out to be useful only to create the object that is referenced at first use, for example. use lazy loading.

I often use this template to create a lazy loaded property:

Encoding utf8NoBomEncoding; Encoding Utf8NoBomEncoding { get { return this.utf8NoBomEncoding ?? (this.utf8NoBomEncoding = new UTF8Encoding(false)); } } 

Then I came across this code while looking at the source code for BCL:

 Encoding Utf8NoBomEncoding { get { if (this.utf8NoBomEncoding == null) { var encoding = new UTF8Encoding(false); Thread.MemoryBarrier(); this.utf8NoBomEncoding = encoding; } return this.utf8NoBomEncoding; } } 

As far as I can tell, none of them are thread safe. For instance. Multiple Encoding objects can be created. I fully understand this and know that this is not a problem if an additional Encoding object is created. It is unchanged and will soon be garbage collection.

However, I am very interested to understand why Thread.MemoryBarrier necessary and how the second implementation differs from the first in scripts with multiple threads.

Obviously, if thread safety is an issue, a better option would probably be to use Lazy<T> :

 Lazy<Encoding> lazyUtf8NoBomEncoding = new Lazy<Encoding>(() => new UTF8Encoding(false)); Encoding Utf8NoBomEncoding { get { return this.lazyUtf8NoBomEncoding.Value; } } 
+4
source share
2 answers

This code will be a disaster without a memory barrier. Look carefully at these lines of code.

  var encoding = new UTF8Encoding(false); Thread.MemoryBarrier(); this.utf8NoBomEncoding = encoding; 

Now imagine that some other thread sees the effects of the last line, but does not see the effects of the first line. That would be a complete disaster.

The memory limit ensures that any thread that sees encoding also sees all the effects of its constructor.

For example, without a memory barrier, the first and last lines can be internally optimized (roughly) as follows:
1) Select some memory, save a pointer to it in this.utf8NoBomEncoding
2) Call the UTF8Encoding constructor to fill this memory with valid values.

Imagine if a different thread runs between steps 1 and 2 and passes through this code. It will use an object that has not yet been created.

+6
source

This pattern is fairly common in .NET. This is possible because UTF8Encoding is an immutable class. Yes, multiple instances of the class can be created, but it does not matter, since all instances are the same. Done with an override of Equals (). Additional copies will quickly collect garbage. The memory limit simply provides full visibility of the state of the object.

+2
source

Source: https://habr.com/ru/post/1380407/


All Articles