Question

When designing a class that has a reference to another object it might be beneficial to only create the referenced object the first time it is used, e.g. use lazy loading.

I often use this pattern 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 when browsing the source code for the 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 these are thread safe. E.g. multiple Encoding objects may be created. I completely get that and know that it isn't a problem if an extra Encoding object is created. It is immutable and will soon be garbage collection.

However, I'm really curious to understand why Thread.MemoryBarrier is necessary and how the second implementation is different from the first in multi-threading scenarios.

Obviously, if thread safety is a concern the best implementation is probably to use Lazy<T>:

Lazy<Encoding> lazyUtf8NoBomEncoding = 
  new Lazy<Encoding>(() => new UTF8Encoding(false));

Encoding Utf8NoBomEncoding {
  get {
    return this.lazyUtf8NoBomEncoding.Value;
  }
}
Was it helpful?

Solution

This code would be a disaster without the memory barrier. Look closely at these lines of code.

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

Now, imagine some other thread sees the effects of the last line but doesn't see the effects of the first line. That would be a complete disaster.

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

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

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

OTHER TIPS

This pattern is fairly common in .NET. It is possible because UTF8Encoding is an immutable class. Yes, more than one instance of the class can be created but that does not matter since all instances are the same. Enforced with an Equals() override. The extra copies will be quickly garbage collected. The memory barrier just ensures that the object state is fully visible.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top