8

我今天在我们的代码库中遇到了一些单例代码,我不确定以下是否是线程安全的:

public static IContentStructure Sentence{ 
    get {
       return _sentence ?? (_sentence = new Sentence()); 
    }
}

该语句等价于:

if (_sentence != null) {
       return _sentence;
}
else {
    return (_sentence = new Sentence());
}

我相信 ??只是一个编译器技巧,生成的代码仍然不是原子的。换句话说,两个或多个线程可以在将 _sentence 设置为新的 Sentence 并返回之前发现 _sentence 为空。

为了保证原子性,我们必须锁定那段代码:

public static IContentStructure Sentence{ 
    get {

       lock (_sentence) { return _sentence ?? (_sentence = new Sentence()); }
    }
}

这一切都正确吗?

4

3 回答 3

16

我今天在我们的代码库中遇到了一些单例代码

你的代码库中是否有这样的混淆代码?这段代码做同样的事情:

if (_s == null) 
    _s = new S();
return _s;

并且阅读起来容易上千倍。

我相信 ??只是一个编译器技巧,生成的代码仍然不是原子的

你是对的。C# 对原子性做出以下保证:

以下数据类型的读写是原子的:bool、char、byte、sbyte、short、ushort、uint、int、float 和引用类型。此外,上一个列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读取和写入,包括 long、ulong、double 和 decimal,以及用户定义的类型,不保证是原子的。除了为此目的设计的库函数之外,不能保证原子读-修改-写,例如在递增或递减的情况下。

空合并运算符不在该保证列表中。

为了保证原子性,我们必须锁定那段代码:

lock (_sentence) { return _sentence ?? (_sentence = new Sentence()); } } }    

天哪,没有。立马就崩溃了!

正确的做法是其中之一:

  • 停止尝试编写多线程代码。
  • 使用 Jon Skeet 在他关于单例的页面上的文档中的一种安全单例模式编写单例。
  • 使用Lazy<T>类。
  • 锁定专用于锁定该变量的对象。
  • 使用联锁比较交换进行原子测试和设置。
于 2012-02-23T22:12:43.200 回答
13

你是对的; 它根本不是线程安全的。

于 2012-02-23T19:58:41.110 回答
1

您可以使用Interlocked.CompareExchangenull获得一个??原子的 -esque 操作。

// I made up my own Sentence type
Sentence current = null;
var whenNull = new Sentence() {Text = "Hello World!"};

var original = Interlocked.CompareExchange(ref current, new Sentence() { Text = "Hello World!" }, null);

Assert.AreEqual(whenNull.Text, current.Text);
Assert.IsNull(orig);

// try that it won't override when not null
current.Text += "!";
orig = Interlocked.CompareExchange(ref current, new Sentence() { Text = "Hello World!" }, null);

Assert.AreEqual("Hello World!!", current.Text);
Assert.IsNotNull(orig);
于 2012-02-23T20:07:03.013 回答