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 VolatileRead
s and writes to be VolatileWrite
s, 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
How to Compare Two Rich Text Box Contents and Highlight the Characters That Are Changed
How to Make Multi-Language App in Winforms
Using the Iterator Variable of Foreach Loop in a Lambda Expression - Why Fails
Avoiding First Chance Exception Messages When the Exception Is Safely Handled
How to Combine More Than Two Generic Lists in C# Zip
Parsing Ftpwebrequest Listdirectorydetails Line
Oledbparameters and Parameter Names
Find a Control in Windows Forms by Name
Properly Draw Text Using Graphicspath
Escape Special Character in Regex
Export to Excel from a List with Epplus
How to Update Textbox in Form1 from Form2