【发布时间】:2010-12-14 15:30:09
【问题描述】:
换句话说,我可以对一个普通变量和 Interlocked 类也无法解决的 volatile 变量做一些事情吗?
【问题讨论】:
-
我知道 volatile 有什么好处。问题是是否存在不能用互锁方法替换 volatile 的情况?
标签: c# .net multithreading volatile interlocked
换句话说,我可以对一个普通变量和 Interlocked 类也无法解决的 volatile 变量做一些事情吗?
【问题讨论】:
标签: c# .net multithreading volatile interlocked
是的 - 您可以直接查看值。
只要您只使用 Interlocked 类来访问变量,就没有区别。 volatile 的作用是告诉编译器该变量是特殊的,并且在优化时不应假定该值没有改变。
执行此循环:
bool done = false;
...
while(!done)
{
... do stuff which the compiler can prove doesn't touch done...
}
如果您在另一个线程中将done 设置为true,您会期望循环退出。但是 - 如果 done 没有标记为 volatile,那么编译器可以选择意识到循环代码永远不会更改 done,并且它可以优化退出比较。
这是多线程编程的难点之一——许多问题只会在特定情况下出现。
【讨论】:
编辑:很大程度上重写了问题
为了回答这个问题,我对此事进行了深入研究,发现了一些我不知道的关于 volatile 和 Interlocked 的事情。让我们澄清这一点,不仅是为了我,也是为了这个讨论和其他阅读这个的人:
volatile 读/写应该不受重新排序的影响。这只表示读和写,确实不表示任何其他动作;Interlocked 对 CompareExchange (cmpxchg)、Increment (inc) 等使用原子汇编指令;Interlocked 确实使用锁有时:hardware lock on multi processor systems;在单处理器系统中,没有硬件锁;Interlocked 与 volatile 的不同之处在于它使用了全栅栏,而 volatile 使用了半栅栏。volatile 时。 Interlocked 不会发生这种情况。 VolatileRead 和 VolatileWrite 与 `volatile 有相同的重新排序问题(链接感谢 Brian Gideon)。既然我们有了规则,我们就可以为您的问题定义一个答案:
volatile 做而你不能用Interlocked 做:
a = b,其中a或b是易失性的,但这很明显;Interlocked 无法做到这一点。换句话说:使用volatile,您可以更少安全,然后使用Interlocked。volatile 比 Interlocked 更快。语义上:不,因为Interlocked 只是提供了一个超集操作并且使用起来更安全,因为它应用了完整的防护。你不能用volatile 做任何你不能用Interlocked 做的事情,你可以用Interlocked 做很多事情,而你不能用 volatile 做:
static volatile int x = 0;
x++; // non-atomic
static int y = 0;
Interlocked.Increment(y); // atomic
作用域:是的,声明一个变量volatile 使其在每次访问时都是可变的。无法以任何其他方式强制执行此行为,因此无法将volatile 替换为Interlocked。这在其他库、接口或硬件可以访问您的变量并随时更新它或需要最新版本的情况下是必需的。
如果你问我,最后一点是volatile 的真正需要,并且可能使其成为两个进程共享内存并且需要在不锁定的情况下读取或写入的理想选择。在这种情况下,将变量声明为volatile 会更安全,然后强制所有程序员使用Interlocked(编译器无法强制)。
编辑:以下引用是我原始答案的一部分,我将其保留在 ;-)
引用C#编程语言标准:
对于非易失性字段,优化 考虑重新排序的技术 指令可能会导致意外 和不可预知的结果 访问的多线程程序 没有同步的字段,例如 由 lock-statement 提供。 这些优化可以通过 编译器,由运行时系统, 或通过硬件。对于易失性领域, 这种重新排序优化是 受限:
对易失性字段的读取称为易失性读取。易失性读取 有 :acquire 语义”;也就是说,它 保证发生在任何之前 对之后发生的内存的引用 它在指令序列中。
对易失性字段的写入称为易失性写入。一种 易失性写入具有“释放 语义”;也就是说,它是有保证的 在任何内存引用之后发生 在写指令之前 指令顺序。
更新:问题在很大程度上被重写,更正了我原来的回答并添加了一个“真实”的答案
【讨论】:
CompareExchange(认为它不是原子的)SSCLI(共享源CLI)表明它最终会转换为x86处理器上的cmpxchg汇编指令,并且该指令——就像Interlocked中的任何其他指令一样——确实原子的。
这是一个相当复杂的话题。我发现 Joseph Albahari 的 writeup 是 .NET Framework 中多线程概念的更权威和准确的来源之一,可能有助于回答您的问题。
但是,快速总结一下,volatile 关键字和 Interlocked 类之间在如何使用方面存在很多重叠。当然,两者都远远超出了普通变量所能做的事情。
【讨论】:
我不会试图成为这个主题的权威,但我强烈建议您看看自吹自擂的 Jon Skeet 的 this article。
还可以查看this answer 的最后部分,其中详细说明了volatile 的用途。
【讨论】:
是的,您可以通过使用 volatile 变量而不是锁来获得一些性能。
Lock 是一个完整的内存屏障,它可以为您提供与 volatile 变量以及许多其他变量相同的特性。正如已经说过的那样,volatile 只是确保在多线程场景中,如果 CPU 更改其缓存行中的值,其他 CPU 会立即看到该值,但根本不确保任何锁定语义。
问题是锁比 volatile 更强大,你应该尽可能使用 volatile 来避免不必要的锁。
【讨论】: