C# bool 是原子的,为啥 volatile 有效

Posted

技术标签:

【中文标题】C# bool 是原子的,为啥 volatile 有效【英文标题】:C# bool is atomic, why is volatile validC# bool 是原子的,为什么 volatile 有效 【发布时间】:2016-11-05 01:58:25 【问题描述】:

C# 中,我们知道 bool 是原子的 - 那么为什么将其标记为 volatile 是有效的?两者有什么区别,什么是好的(甚至实用的)用例?

bool _isPending;

对比

volatile bool _isPending; // Is this realistic, or insanity?

我已经阅读了here 和here,我正在努力确保我完全理解这两者的内部工作原理。我想了解什么时候使用一个与另一个比较合适,或者仅bool 是否足够。

【问题讨论】:

我很确定对于每个volatile bool 用例,都存在另一个更优化的解决方案。所以理论上bool 就足够了。 理论上,理论和实践没有区别。但实际上确实如此。 @Tdorno 事实并非如此。 volatile bool 是安全发布数据的最便宜和最快的方法(最简单的示例是退出标志)。 @vmatm 仅当环境中某处存在错误时。在这种情况下,两个版本在理论和实践上存在巨大差异 @Voo 你能说得更具体点吗?可能是一些文章或博客文章或 MDSN? 【参考方案1】:

在 C# 中,我们知道 bool 是原子的 - 那么为什么将它标记为 volatile 是有效的呢?两者有什么区别,什么是好的(甚至实用的)用例?

您的问题的假设是您相信volatile 使访问原子化。但是波动性和原子性是完全不同的东西,所以不要把它们混为一谈。

波动性是编译器和运行时被限制进行某些优化的属性,这些优化涉及在时间上相对于彼此向前和向后移动变量的读取和写入,更一般地,关于其他重要事件,例如启动和停止线程、运行构造函数等。请参阅 C# 规范,了解有关可见副作用如何重新排序操作的详细列表。

原子性是特定操作只能被观察为未开始或完全完成的属性,并且永远不会“半途而废”。

从定义中可以看出,这两件事彼此之间没有任何关系任何

在 C# 中,所有对大小为 4 或更小的引用、布尔值和整数类型的访问都保证是原子的。

现在,在 C# 中,原子性和易失性之间存在一些轻微的非正交性,因为只有原子类型的字段可能被标记为易失性。例如,您不能制作 volatile double。说“我们将限制如何优化读写但仍然允许撕裂”真的很奇怪和危险。由于波动性不会导致原子性,因此您不想让用户仅仅因为它也是可变的,就认为操作是原子的。

您应该阅读我的系列文章,这些文章更详细地解释了这些事物之间的区别,以及 volatile 的实际作用,以及为什么您对安全使用它的理解还不够。

https://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one/

https://ericlippert.com/2011/05/31/atomicity-volatility-and-immutability-are-different-part-two/

https://ericlippert.com/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/

https://web.archive.org/web/20160323025740/http://blog.coverity.com/2014/03/12/can-skip-lock-reading-integer/

如果您在阅读完所有内容后认为您了解波动性,我邀请您尝试解决我在这里提出的难题:

https://web.archive.org/web/20160729162225/http://blog.coverity.com/2014/03/26/reordering-optimizations/

【讨论】:

感谢您的详细解答。我已经阅读了您分享的文章,它们非常有帮助。 @DavidPine:不客气;乐意效劳!这是一个非常棘手的主题。【参考方案2】:

如果前面或后续代码中的变量有更新,并且更新发生的顺序很关键,那么将该字段标记为 volatile 将确保对该字段的更新将在任何先前的更新之后和之前发生任何后续更新。

换句话说,如果_isPendingvolatile,那么编译器不会导致这些指令以不同的顺序执行:

_someVariable = 10;
_isPending = true;
_someOtherVariable = 5;

无论是否是多线程的,如果我们编写的代码会根据相邻行中的这些更新是否以指定的顺序发生而中断,那么就有问题了。我们应该问为什么这个顺序很重要。 (如果有一个重要的场景,想象一下尝试在评论中解释它,以便没有人对代码进行重大更改。)

对于几乎所有阅读上述代码的人来说,这些操作的顺序似乎根本不重要。如果它们确实重要,那意味着阅读我们代码的其他人可能无法理解发生了什么。他们可以进行一些重构,重新排序这些代码行,并在不知不觉中破坏一切。它甚至可能在他们对其进行测试时工作,然后在部署时出现不可预测且不一致的失败。

我同意您链接的答案中的Eric Lippert's comment:

坦率地说,我不鼓励你创建一个不稳定的字段。易挥发的 字段表明你正在做一些非常疯狂的事情:你是 试图在两个不同的线程上读取和写入相同的值 没有锁到位。


我想我没有直接回答方向。 volatile 对一个类型(包括bool)有效,因为可以对该类型执行原子操作。 volatile 防止编译器优化。根据volatile 的文档,

这确保了最新的值出现在字段中 任何时候。

但如果该字段不能以 32 位或更少位表示,那么阻止编译器优化也无法保证这一点。

【讨论】:

Volatile 不仅仅是禁止重新排序指令!简化它提供了排序和可见性保证。仅仅因为您以正确的顺序执行上述指令通常不能保证“预期”的结果(它可能适用于 x86,但这是规则的例外)。 是的,这只会加强 rhe 点。如果代码依赖于 volatile 的行为,那么它本质上更容易失败。如果他们只是使用锁,那么它在哪个处理器上运行都没有关系。 如果您正确使用 volatile,您会在每个平台上获得相同的行为。如果他们编写了不正确的带有锁定的代码,那么它会以非确定性的方式失败。确实,用低级原语编写正确的代码非常困难,最好避免。 @ScottHannen 谢谢你的回答。

以上是关于C# bool 是原子的,为啥 volatile 有效的主要内容,如果未能解决你的问题,请参考以下文章

volatile为啥不能保证原子性

是 C# 中的 bool 读/写原子

为啥 C# 中 bool 数据类型的大小不是只有 1 位?

java单例双重检查锁为啥需要加volatile关键字

为啥在双重检查锁定中使用 Volatile.Write?

为啥c#中bool要占4个字节 32位呢 为啥不用像byte 1个字节存储呢