Multithreading: using VolatileXXX instead of the volatile keyword
[Update: Brad detected a flaw in the code: I had forgotten to initialize the _initialized field. Thanks!]
[Update2: Brad detected another flaw in the code: return _instance must be outside the if. Thanks!]
In the previous post we’ve seen how we can use the C# volatile keyword to guarantee that those nasty load-load reordering stay away from our code. As I’ve said before, we can also use the static Thread.VolatileRead or Thread.VolatileWrite for having more control over the way fences are applied to our code. Going back to our previous volatile example, the question is: do we really need a fence whenever we access our instance variable?
Looking at the code, I guess that we can get away by just using an acquire fence on the initialization of the instance. Recall that an acquire fence is an optimization of the full fence and ensures that no load or store that comes after the fence can be moved before the fence (it’s just what we need to ensure proper initialization and eliminate the possible load/load reorderings allowed by the CLR).
With this in mind, lets update our sample, ok? Btw, we’ll be using another variable for controlling initialization (we’re picking an integer). This is your best option for initializing value types since you can’t control it size or check it for null (don’t forget our previous discussion on word size, alignment and .NET). Here’s the final code:
class Lazy{
private Object _locker = new Object();
private SomeObject _instance = null;
private Int32 _initialized = 0;
public SomeObject SomeObject {
get {
if (Thread.VolatileRead(ref _initialized) == 0) {
lock (_locker) {
if (_initialized == 0) {
_instance = new SomeObject();
_initialized = 1;
}
}
}
return _instance;
}
}
}
This code is also correct and will behave properly in all the current architectures that run Windows and the CLR. There’s no need for running another VolatileRead on the inner comparison due to a thing called control dependency (check this post by Joe Duffy for more info). Notice that in these posts our main objective is ensuring that you end up getting only one instance of a specific type. As I’ve said, if you don’t care about creating multiple instances and only need to ensure that you’ll have only one active instance, you can only use the Interlocked.CompareExchange method for that. We’ll see how in the next post. Keep tuned!