The Need for Volatile Modifier in Double Checked Locking in .Net

The need for volatile modifier in double checked locking in .NET

Volatile is unnecessary. Well, sort of**

volatile is used to create a memory barrier* between reads and writes on the variable.

lock, when used, causes memory barriers to be created around the block inside the lock, in addition to limiting access to the block to one thread.

Memory barriers make it so each thread reads the most current value of the variable (not a local value cached in some register) and that the compiler doesn't reorder statements. Using volatile is unnecessary** because you've already got a lock.

Joseph Albahari explains this stuff way better than I ever could.

And be sure to check out Jon Skeet's guide to implementing the singleton in C#


update:

*volatile causes reads of the variable to be VolatileReads and writes to be VolatileWrites, which on x86 and x64 on CLR, are implemented with a MemoryBarrier. They may be finer grained on other systems.

**my answer is only correct if you are using the CLR on x86 and x64 processors. It might be true in other memory models, like on Mono (and other implementations), Itanium64 and future hardware. This is what Jon is referring to in his article in the "gotchas" for double checked locking.

Doing one of {marking the variable as volatile, reading it with Thread.VolatileRead, or inserting a call to Thread.MemoryBarrier} might be necessary for the code to work properly in a weak memory model situation.

From what I understand, on the CLR (even on IA64), writes are never reordered (writes always have release semantics). However, on IA64, reads may be reordered to come before writes, unless they are marked volatile. Unfortuantely, I do not have access to IA64 hardware to play with, so anything I say about it would be speculation.

i've also found these articles helpful:

http://www.codeproject.com/KB/tips/MemoryBarrier.aspx

vance morrison's article (everything links to this, it talks about double checked locking)

chris brumme's article (everything links to this)

Joe Duffy: Broken Variants of Double Checked Locking

luis abreu's series on multithreading give a nice overview of the concepts too

http://msmvps.com/blogs/luisabreu/archive/2009/06/29/multithreading-load-and-store-reordering.aspx

http://msmvps.com/blogs/luisabreu/archive/2009/07/03/multithreading-introducing-memory-fences.aspx

What kind of 'volatile' operation is needed in Double checked locking in .NET

First, messing with volatile is really hard, so don't get too loose with it! But, here is a really close answer to your question, and here is an article that I think everyone should read before using the keyword volatile, and definitely before starting to use VolatileRead, VolatileWrite and MemoryBarrier.

The answer in the first link is: no you don't need to use volatile, you just need to use System.Threading.Thread.MemoryBarrier() RIGHT BEFORE you assign the new value. This is because the release_fence implied when using the volatile keyword makes sure that it gets finished writing out to the main memory, and that no read/write operations can be performed until it's finished.

So, what does Thread.VolatileWrite() do, and does it perform the same functions that we get from the 'volatile' keyword? Well, here's the full code from this function:

public static void VolatileWrite (ref int address, int value)
{
MemoryBarrier(); address = value;
}

Yes, it calls MemoryBarrier right before it assigns your value, which is sufficient!

Double-checked locking in .NET

Implementing the Singleton Pattern in C# talks about this problem in the third version.

It says:

Making the instance variable volatile can make it work, as would explicit memory barrier calls, although in the latter case even experts can't agree exactly which barriers are required. I tend to try to avoid situations where experts don't agree what's right and what's wrong!

The author seems to imply that double locking is less likely to work than other strategies and thus should not be used.

Why is this double-checked locking correct? (.NET)

I think the key is in the linked article (http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S5). Specifically that the MS-implemented .NET 2.0 memory model has the following property:

Writes cannot move past other writes from the same thread.

Duffy mentions that a lot of work was done to support this on the IA-64:

We accomplish this by ensuring writes have 'release' semantics on IA-64, via the st.rel instruction. A single st.rel x guarantees that any other loads and stores leading up to its execution (in the physical instruction stream) must have appeared to have occurred to each logical processor at least by the time x's new value becomes visible to another logical processor. Loads can be given 'acquire' semantics (via the ld.acq instruction), meaning that any other loads and stores that occur after a ld.acq x cannot appear to have occurred prior to the load.

Note that Duffy also mentions that this is an MS-specific guarantee - it's not part of the ECMA spec (at least as of the the article's writing in 2006). So, Mono might not be as nice.

Again double-checked locking and C#

To emphasize the point @Mannimarco makes: if this is the only access point to the Value, and it looks that way, then your whole ReaderWriterLockSlim setup is no better than a simple Monitor.Enter / Monitor.Leave approach. It is a lot more complicated though.

So I believe the following code is equivalent in function and efficiency:

private WeakReference _valueReference = new WeakReference(null);
private object _locker = new object();

public MyType Value
{
get
{
lock(_locker) // also provides the barriers
{
value = _valueReference.Target;

if (!_valueReference.IsAlive)
{
_valueReference = new WeakReference(value = InitializeMyType());
}
return value;
}
}
}

Memory Model Guarantees in Double-checked Locking

The big problem with the example is that the first null check is not locked, so instance may not be null, but before Init has been called. This may lead to threads using instance before Init has been called.

The correct version should therefore be:

public static Foo GetValue()
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
var foo = new Foo();
foo.Init();
instance = foo;
}
}
}

return instance;
}


Related Topics



Leave a reply



Submit