C#凝聚算子的原子性

本文关键字:原子性 凝聚 | 更新日期: 2023-09-27 18:25:32

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

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

此语句相当于:

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

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

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

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

这都对吗?

C#凝聚算子的原子性

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

你的代码库中有这样的模糊代码吗?这个代码做同样的事情:

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

阅读起来容易一千倍。

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

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

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

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

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

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

天哪,不。它马上就崩溃了!

正确的做法是:

  • 停止尝试编写多线程代码
  • 使用Jon Skeet在其关于singleton的页面上文档中的一个安全singleton模式编写singleton
  • 使用Lazy<T>
  • 锁定专用于锁定该变量的对象
  • 使用Interlocked Compare Exchange进行原子测试和设置

您是正确的;它根本不安全。

您可以将Interlocked.CompreExchange与null一起使用,以获得原子的??式操作。

// 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);