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

a 夏天 提交于 2019-12-07 02:30:10

问题


I have read a lot about the dangers of double checked locking and I would try hard to stay away of it, but with that said I think they make a very interesting read.

I was reading this article of Joe Duffy about implementing singleton with double checked locking: http://www.bluebytesoftware.com/blog/PermaLink,guid,543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx

And the (variant of) solution he seemed to propose 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, doesn't that still have the danger of writes being reordered? Specifically these two lines:

instance = new Singleton();
initialized = 1;

If those writes are inverted, then some other thread can still read null.


回答1:


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.




回答2:


Initial Comments

I do not necessarily think the author of that article was actually proposing that this variation of the double-checked locking pattern be used per se. I think he was just pointing that it is one variation that might be considered by an naive developer to tackle the problem in the context of value types.

Value types obviously cannot store null values so another variable must be used to signal the completion of initialization. The author mentions all of this and then confusingly talks about reading instance as null. Presumably, the author was thinking of a really naive developer who used this variation incorrectly on value types at one time and then continued to apply it, also incorrectly, for reference types. In the case of a value type a thread could read and use a struct with default field initializations when that was not intended. In the case of reference types a thread could read and use null instance.

The use of Thread.VolatileRead was the author's proposal to fix this variation. Without the volatile read the read of instance in the return statement could lifted before the read of initialized like this.

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;
    }
  }
}

Hopefully the above reordering of the code clearly demonstrates the problem. And equal obvious it should be that a volatile read of initialized prevents the read of instance from being lifted.

And again, I think the author was merely showing one possible way to fix this particular variation and not that the author was advocating this approach in general.

Answering your questions

My question is, doesn't that still have the danger of writes being reordered?

YES (qualified): As your have correctly pointed out the writes to instance and initialized could be swapped inside the lock. Worse still, the writes that may be going on inside Singleton.ctor could also occur out of order in such a manner that instance gets assigned before the instance is fully initialized. Another thread could see instance set, but that instance may be in a partially constructed state.

However, writes in Microsoft's implementation of the CLI have release-fence semantics. Meaning everything I just said does not apply when using the .NET Framework runtime on any hardware platform. But, an obscure environment like Mono running on ARM could exhibit the problematic behavior.

The author's use of Thread.VolatileRead to "fix" this variation would not work in general because it does nothing to solve the problem of reordered writes. The code is not 100% portable. This is one reason why I doubt the author was proposing this variation.

The canonical variation of using a single instance variable in conjunction with volatile is obviously the correct solution. The volatile keyword has acquire-fence semantics on reads and release-fence semantics on writes so it solves both problems; the one you identified and the one addressed by the article.




回答3:


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

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

is guaranteed to be thread-safe

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



来源:https://stackoverflow.com/questions/8350713/why-is-this-double-checked-locking-correct-net

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!