Why is this double lock check correct? (.NET)

I read a lot about the dangers of double locking, and I tried to stay away from it, but with that said that I think they are doing a very interesting reading.

I read this article by Joe Duffy about introducing a double checked locking singleton: http://www.bluebytesoftware.com/blog/PermaLink,guid,543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx

And the option (option) that he seemed to be offering is this:

class Singleton { private static object slock = new object(); private static Singleton instance; private static int initialized; private Singleton() {} public Instance { get { if (Thread.VolatileRead(ref initialized) == 0) { lock (slock) { if (initialized == 0) { instance = new Singleton(); initialized = 1; } } } return instance; } } 

}

My question is, is there still a danger that the records will be reordered? In particular, these two lines:

 instance = new Singleton(); initialized = 1; 

If these records are inverted, then another thread can still read zero.

+7
source share
3 answers

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

Scriptures cannot move past other records from the same stream.

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

We achieve this by ensuring that the entries have โ€œreleaseโ€ semantics on IA-64 through the st.rel statement. The only st.rel x guarantees that any other loads and repositories leading to its execution (in the flow of the physical instruction) should appear for each logical processor, at least by the time the new value becomes visible to the other logical processor. Loads can be specified by the semantics of โ€œacquireโ€ (using the ld.acq command), which means that any other loads and storages that occur after ld.acq x cannot appear before loading.

Please note that Duffy also mentions that this is a specific MS warranty - it is not part of the ECMA specification (at least in an article written in 2006). So Mono may not be so enjoyable.

+5
source

Initial comments

I donโ€™t necessarily think that the author of this article actually suggested using this variation of the double-checked lock pattern. I think he simply pointed out that this is one option that can be considered by a naive developer to solve the problem in the context of value types.

Value types, obviously, cannot store null values, so you need to use another variable to confirm the completion of initialization. The author mentions all this, and then vaguely talks about reading instance as null . Presumably, the author was thinking of a really naive developer who used this change incorrectly by value type at a time, and then continued to apply it, and also incorrectly, for reference types. In the case of a value type, the stream can read and use struct with initialization of the default field, if it is not intended. In the case of reference types, a stream can read and use an instance of null .

The use of Thread.VolatileRead was proposed by the author to correct this option. Without a volatile read, reading instance in the return statement can be removed before reading initialized as follows.

 class Singleton { private static object slock = new object(); private static Singleton instance; private static int initialized; private Singleton() {} public Instance { get { var local = instance; if (initialized == 0) { lock (slock) { if (initialized == 0) { instance = new Singleton(); initialized = 1; } } } return local; } } } 

We hope that the aforementioned reordering of the code clearly demonstrates the problem. And it is obvious that an intermittent reading of initialized prevents instance reading.

And again, I think that the author simply showed one of the possible ways to correct this particular option, and not that the author advocated this approach as a whole.

Answering your questions

My question is, is there still a danger that recordings will be ordered?

YES (qualified): since you correctly pointed out that the records in instance and initialized can be replaced inside lock . Worse, entries that can occur inside Singleton.ctor can also fail so that instance assigned before the instance is fully initialized. Another thread might see the instance set, but this instance may be in a partially constructed state.

However, Microsoft entries for CLI implementations have release fence semantics. The meaning of everything I just said does not apply when using the .NET Framework runtime on any hardware platform. But an obscure environment such as Mono running on ARM may exhibit problematic behavior.

An author using Thread.VolatileRead to โ€œfixโ€ this option will not work at all because he does nothing to solve the reordered write problem. The code is not 100% portable. This is one of the reasons why I doubt that the author suggested this variation.

The canonical variation of using a single instance variable in combination with volatile is obviously the right solution. The volatile keyword has the semantics of capture semantics when reading and the release semantics for writing, so it solves both problems; the one you identified and the one that is listed in the article.

+1
source

According to http://msdn.microsoft.com/en-us/library/ee817670.aspx singleton similar

 // .NET Singleton sealed class Singleton { private Singleton() {} public static readonly Singleton Instance = new Singleton(); } 

guaranteed to be thread safe

Framework internally guarantees thread safety when initializing a static type. [..] In the Framework itself, there are several classes that use this singleton type, although the name of the property is used instead of Value. The concept is exactly the same.

-one
source

All Articles