C# 合并运算符的原子性

发布于 2025-01-08 04:16:00 字数 650 浏览 1 评论 0原文

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

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

此语句相当于:

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

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

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

public static IContentStructure Sentence{ 
    get {

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

这一切都正确吗?

I ran into some singleton code today in our codebase and I wasn't sure if the following was thread-safe:

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

This statement is equivalent to:

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

I believe that ?? is just a compiler trick and that the resulting code is still NOT atomic. In other words, two or more threads could find _sentence to be null before setting _sentence to a new Sentence and returning it.

To guarantee atomicity, we'd have to lock that bit of code:

public static IContentStructure Sentence{ 
    get {

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

Is that all correct?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

双手揣兜 2025-01-15 04:16:00

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

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

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 类。
  • 锁定专用于锁定该变量的对象。
  • 使用互锁比较交换进行原子测试和设置。

I ran into some singleton code today in our codebase

Do you have such obfuscated code throughout your codebase? This code does the same thing:

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

and is about a thousand times easier to read.

I believe that ?? is just a compiler trick and that the resulting code is still NOT atomic

You are correct. C# makes the following guarantees of atomicity:

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement.

The null coalescing operator is not on that list of guarantees.

To guarantee atomicity, we'd have to lock that bit of code:

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

Good heavens no. That crashes immediately!

The correct thing to do is one of:

  • Stop trying to write multithreaded code.
  • Write a singleton using one of the safe singleton patterns Jon Skeet documents on his page about singletons.
  • Use the Lazy<T> class.
  • Lock on an object dedicated to locking that variable.
  • Use an Interlocked Compare Exchange to do an atomic test and set.
好菇凉咱不稀罕他 2025-01-15 04:16:00

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

You are correct; it's not at all thread-safe.

韵柒 2025-01-15 04:16:00

您可以将 Interlocked.CompareExchangenull 一起使用获得一个 ?? 式的原子操作。

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

You can use Interlocked.CompareExchange with null to get a ??-esque operation that is atomic.

// 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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文