The need for volatile modifier in double checked locking in .NET
Asked Answered
B

8

94

Multiple texts say that when implementing double-checked locking in .NET the field you are locking on should have volatile modifier applied. But why exactly? Considering the following example:

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}

why doesn't "lock (syncRoot)" accomplish the necessary memory consistency? Isn't it true that after "lock" statement both read and write would be volatile and so the necessary consistency would be accomplished?

Belgium answered 27/12, 2009 at 0:13 Comment(6)
This has been chewed on many times already. yoda.arachsys.com/csharp/singleton.htmlHydrolyte
Unfortunately in that article Jon references 'volatile' twice, and neither reference directly refers to the code examples he gave.Indecorum
See this article to understand the concern: igoro.com/archive/volatile-keyword-in-c-memory-model-explained Basically it is THEORETICALLY possible for the JIT to use a CPU register for the instance variable - especially if you needed a little extra code in there. Thus doing an if statement twice potentially could return the same value regardless of it changing on another thread. In reality the answer is a little complex the lock statement may or may not be responsible for making things better here (continued)Hofstetter
(see previous comment continued) - Here is what I think is really happening - Basically any code that does anything more complex than read or set a variable may trigger the JIT to say, forget trying to optimize this let's just load and save to memory because if a function is called the JIT would potentially need to save and reload the register if it stored it there each time, rather than do that it just writes and reads directly from memory each time. How do I know lock is nothing special? Look at the link I posted in the previous comment from Igor (continued next comment)Hofstetter
(see above 2 comments) - I tested Igor's code and when it creates a new thread I added a lock around it and even made it loop. It still wouldn't cause the code to exit because the instance variable was hoisted out of the loop. Adding to the while loop a simple local variable set still hoisted the variable out of the loop - Now anything more complicated like if statements or a method call or yes even a lock call would prevent the optimization and thus make it work. So any complex code often forces direct variable access rather than allowing the JIT to optimize. (continued next comment)Hofstetter
(see above 3 comments) If they ever make the JIT optimize better its possible if volatile is not used your double check locking may stop working (or if you use Mono it won't work or run on ARM platforms it likely won't work either.) If people read to the end of the most quoted (Van M) article that proposes not using volatile at the end he says you should just use volatile to be safe.Hofstetter
I
63

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

Irregular answered 27/12, 2009 at 1:6 Comment(8)
Jon Skeet actually says that volatile modifier is needed to create proper memory barier, while the first link author says that lock (Monitor.Enter) would be sufficient. Who is actually right???Belgium
@Belgium it seems that Jon was referring to the memory model on Itanium 64 processors, so in that case, using volatile may be necessary. However, volatile is unnecessary on x86 and x64 processors. I'll update more in a little bit.Irregular
If lock is indeed making a memory barrier and if memory barier is indeed about both instruction order AND cache invalidating, then it should work on all processors. Anyway it's so strange that such basic thing causes so much confusion...Belgium
I think this is a very extensive coverage of the problem, especially provided links are very useful. My only note is that, as far as I understand from all articles, .NET memory model works the same on all processors, but other CLI implementations (such as Mono) could use other (weaker) memory model. This is achieved using JIT compiler generated instructions.Belgium
This answer looks wrong to me. If volatile was unnecessary on any platform then it would mean the JIT couldn't optimize the memory loads object s1 = syncRoot; object s2 = syncRoot; to object s1 = syncRoot; object s2 = s1; on that platform. That seems very unlikely to me.Rewire
Pretty sure this answer has at least one caveat; the issue is that you don't issue a locking command or memory barrier command when reading, if the object is not null, so there is no memory barrier for the reader without the volatile variable. If the singleton was instantiated with any shared object, the reading thread may have cached, out-of-date versions of that shared object's state, meaning it can read the singleton as if it were in a state it's never been in. A volatile variable prevents that, so long as the read of the volatile variable causes a memory barrier.Jolynjolynn
Even if the CLR wouldn't reorder the writes (I doubt that's true, many very good optimizations can be done by doing so), it'd still be buggy as long as we can inline the constructor call and create the object in-place (we could see a half initialized object). Independent of any memory model the underlying CPU uses! According to Eric Lippert the CLR on Intel at least introduces a membarrier after constructors which denies that optimization, but that's not required by the spec and I wouldn't count on the same thing happening on ARM for example..Withdrawal
this answer is about general use of volatile in a lock. But it's not answering the actual question around the "doublelock pattern". In this pattern you need it as @TheodoreMurdock mentioned. And it's the same reason microsoft also states it in it's documentation - so obviously its not unnecessary.Reciprocation
L
36

There is a way to implement it without volatile field. I'll explain it...

I think that it is memory access reordering inside the lock that is dangerous, such that you can get a not completelly initialized instance outside of the lock. To avoid this I do this:

public sealed class Singleton
{
   private static Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         // very fast test, without implicit memory barriers or locks
         if (instance == null)
         {
            lock (syncRoot)
            {
               if (instance == null)
               {
                    var temp = new Singleton();

                    // ensures that the instance is well initialized,
                    // and only then, it assigns the static variable.
                    System.Threading.Thread.MemoryBarrier();
                    instance = temp;
               }
            }
         }

         return instance;
      }
   }
}

Understanding the code

Imagine that there are some initialization code inside the constructor of the Singleton class. If these instructions are reordered after the field is set with the address of the new object, then you have an incomplete instance... imagine that the class has this code:

private int _value;
public int Value { get { return this._value; } }

private Singleton()
{
    this._value = 1;
}

Now imagine a call to the constructor using the new operator:

instance = new Singleton();

This can be expanded to these operations:

ptr = allocate memory for Singleton;
set ptr._value to 1;
set Singleton.instance to ptr;

What if I reorder these instructions like this:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
set ptr._value to 1;

Does it make a difference? NO if you think of a single thread. YES if you think of multiple threads... what if the thread is interruped just after set instance to ptr:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
-- thread interruped here, this can happen inside a lock --
set ptr._value to 1; -- Singleton.instance is not completelly initialized

That is what the memory barrier avoids, by not allowing memory access reordering:

ptr = allocate memory for Singleton;
set temp to ptr; // temp is a local variable (that is important)
set ptr._value to 1;
-- memory barrier... cannot reorder writes after this point, or reads before it --
-- Singleton.instance is still null --
set Singleton.instance to temp;

Happy coding!

Lambart answered 18/10, 2012 at 0:38 Comment(5)
If the CLR allows access to an object before it is initialized, that's a security hole. Imagine a privileged class whose only public constructor sets "SecureMode = 1" and then whose instance methods check that. If you can call those instance methods without the constructor running, you can break out of the security model and violate sandboxing.Williamwilliams
@MichaelGG: in the case you described, if that class will support multiple threads to access it, then it is a problem. If the constructor call is inlined by the jitter, then it is possible for the CPU to reorder instructions in a way that the stored reference points to a not completely initialized instance. This is not a CLR security problem because it is avoidable, it is the programmer's responsibility to use: interlocked, memory barriers, locks and/or volatile fields, inside the constructor of such class.Lambart
A barrier inside the ctor doesn't fix it. If the CLR assigns the reference to the newly allocated object before the ctor has completed and doesn't insert a membarrier, then another thread could execute an instance method on a semi-initialized object.Williamwilliams
This is the "alternative pattern" that ReSharper 2016/2017 suggests in case of a DCL in C#. OTOH, Java does guarantee that the result of new is fully initialized..Knapweed
I know that MS .net implementation places a memory barrier at the end of the construtor... but its better be safe than sorry.Lambart
H
8

You should use volatile with the double check lock pattern.

Most people point to this article as proof you do not need volatile: https://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10

But they fail to read to the end: "A Final Word of Warning - I am only guessing at the x86 memory model from observed behavior on existing processors. Thus low-lock techniques are also fragile because hardware and compilers can get more aggressive over time. Here are some strategies to minimize the impact of this fragility on your code. First, whenever possible, avoid low-lock techniques. (...) Finally, assume the weakest memory model possible, using volatile declarations instead of relying on implicit guarantees."

If you need more convincing then read this article on the ECMA spec will be used for other platforms: msdn.microsoft.com/en-us/magazine/jj863136.aspx

If you need further convincing read this newer article that optimizations may be put in that prevent it from working without volatile: msdn.microsoft.com/en-us/magazine/jj883956.aspx

In summary it "might" work for you without volatile for the moment, but don't chance it write proper code and either use volatile or the volatileread/write methods. Articles that suggest to do otherwise are sometimes leaving out some of the possible risks of JIT/compiler optimizations that could impact your code, as well us future optimizations that may happen that could break your code. Also as mentioned assumptions in the last article previous assumptions of working without volatile already may not hold on ARM.

Hofstetter answered 23/2, 2015 at 4:18 Comment(2)
Good answer. The only right answer on this question is simple "No". According to this the accepted answer is wrong.Candida
@DennisKassel the accepted answer is right about using volatile in a lock generally. but in the doublelock pattern (whats the actual questsion, it is wrong)Reciprocation
E
7

I don't think anybody has actually answered the question, so I'll give it a try.

The volatile and the first if (instance == null) are not "necessary". The lock will make this code thread-safe.

So the question is: why would you add the first if (instance == null)?

The reason is presumably to avoid executing the locked section of code unnecessarily. While you are executing the code inside the lock, any other thread that tries to also execute that code is blocked, which will slow your program down if you try to access the singleton frequently from many threads. Depending on the language/platform, there could also be overheads from the lock itself that you wish to avoid.

So the first null check is added as a really quick way to see if you need the lock. If you don't need to create the singleton, you can avoid the lock entirely.

But you can't check if the reference is null without locking it in some way, because due to processor caching, another thread could change it and you would read a "stale" value that would lead you to enter the lock unnecessarily. But you're trying to avoid a lock!

So you make the singleton volatile to ensure that you read the latest value, without needing to use a lock.

You still need the inner lock because volatile only protects you during a single access to the variable - you can't test-and-set it safely without using a lock.

Now, is this actually useful?

Well I would say "in most cases, no".

If Singleton.Instance could cause inefficiency due to the locks, then why are you calling it so frequently that this would be a significant problem? The whole point of a singleton is that there is only one, so your code can read and cache the singleton reference once.

The only case I can think of where this caching wouldn't be possible would be when you have a large number of threads (e.g. a server using a new thread to process every request could be creating millions of very short-running threads, each of which would have to call Singleton.Instance once).

So I suspect that double checked locking is a mechanism that has a real place in very specific performance-critical cases, and then everybody has clambered on the "this is the proper way to do it" bandwagon without actually thinking what it does and whether it will actually be necessary in the case they are using it for.

Extinguish answered 27/12, 2009 at 8:55 Comment(4)
This is somewhere between wrong and missing the point. volatile has nothing to do with the lock semantics in double-checked locking, it has to do with the memory model and cache coherence. Its purpose is to ensure that one thread does not receive a value that's still being initialized by another thread, which the double-check lock pattern does not inherently prevent. In Java you definitely need the volatile keyword; in .NET it's murky, because it's wrong according to ECMA but right according the runtime. Either way, the lock definitely does not take care of it.Overfeed
Huh? I can't see where your statement disagrees with what I've said, nor have I said that the volatile is in any way related to lock semantics.Extinguish
Your answer, like several other statements in this thread, claims that the lock makes the code thread-safe. That part is true, but the double-check lock pattern can make it unsafe. That's what you seem to be missing. This answer seems to meander about the meaning and purpose of a double-check lock without ever addressing the thread safety issues which are the reason for volatile.Overfeed
How can it make unsafe if instance is marked with volatile?Or
W
3

AFAIK (and - take this with caution, I'm not doing a lot of concurrent stuff) no. The lock just gives you synchronization between multiple contenders (threads).

volatile on the other hand tells your machine to reevaluate the value every time, so that you don't stumble upon a cached (and wrong) value.

See http://msdn.microsoft.com/en-us/library/ms998558.aspx and note the following quote:

Also, the variable is declared to be volatile to ensure that assignment to the instance variable completes before the instance variable can be accessed.

A description of volatile: http://msdn.microsoft.com/en-us/library/x13ttww7%28VS.71%29.aspx

Willyt answered 27/12, 2009 at 0:20 Comment(1)
A 'lock' also provides a memory barrier, the same as (or better than) volatile.Anthropocentric
R
2

The lock is sufficient. The MS language spec (3.0) itself mentions this exact scenario in §8.12, without any mention of volatile:

A better approach is to synchronize access to static data by locking a private static object. For example:

class Cache
{
    private static object synchronizationObject = new object();
    public static void Add(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
    public static void Remove(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
}
Radiochemistry answered 27/12, 2009 at 9:19 Comment(4)
Jon Skeet in his article (yoda.arachsys.com/csharp/singleton.html) says that volatile is needed for proper memory barrier in this case. Marc, can you comment on this?Belgium
Ah, I hadn't noticed the double-checked lock; simply: don't do that ;-pRadiochemistry
I actually think that double-checked lock is a good thing performance-wise. Also if it is necessary to make a field volatile, while it is accessed within lock, then double-cheked lock is not much worse than any other lock...Belgium
But is it as good as the separate class approach Jon mentions?Radiochemistry
B
2

I think that I've found what I was looking for. Details are in this article - http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10.

To sum up - in .NET volatile modifier is indeed not needed in this situation. However in weaker memory models writes made in constructor of lazily initiated object may be delayed after write to the field, so other threads might read corrupt non-null instance in the first if statement.

Belgium answered 27/12, 2009 at 22:56 Comment(2)
At the very bottom of that article read carefully, especially the last sentence the author states: "A Final Word of Warning - I am only guessing at the x86 memory model from observed behavior on existing processors. Thus low-lock techniques are also fragile because hardware and compilers can get more aggressive over time. Here are some strategies to minimize the impact of this fragility on your code. First, whenever possible, avoid low-lock techniques. (...) Finally, assume the weakest memory model possible, using volatile declarations instead of relying on implicit guarantees."Hofstetter
If you need more convincing then read this article on the ECMA spec will be used for other platforms: msdn.microsoft.com/en-us/magazine/jj863136.aspx If you need further convincing read this newer article that optimizations may be put in that prevent it from working without volatile: msdn.microsoft.com/en-us/magazine/jj883956.aspx In summary it "might" work for you without volatile for the moment, but don't chance it write proper code and either use volatile or the volatileread/write methods.Hofstetter
L
-3

This a pretty good post about using volatile with double checked locking:

http://tech.puredanger.com/2007/06/15/double-checked-locking/

In Java, if the aim is to protect a variable you don't need to lock if it's marked as volatile

Lacreshalacrimal answered 27/12, 2009 at 0:19 Comment(1)
Interesting, but not necessarily very helpful. The JVM's memory model and the CLR's memory model are not the same thing.Course

© 2022 - 2024 — McMap. All rights reserved.