换句话说,我可以用普通变量和 Interlocked 类无法解决的 volatile 变量做一些事情吗?
5 回答
编辑:问题在很大程度上被重写
volatile
为了回答这个问题,我对此事进行了深入研究,发现了一些Interlocked
我不知道的事情。让我们澄清这一点,不仅是为了我,也是为了这个讨论和其他阅读这个的人:
volatile
读/写应该不受重新排序的影响。这仅意味着阅读和写作,并不意味着任何其他动作;- 波动性不是强制在 CPU 上,即硬件级别(x86 在任何读/写时使用获取和释放栅栏)。它确实阻止了编译器或 CLR 优化;
Interlocked
对 CompareExchange (cmpxchg
)、Increment (inc
) 等使用原子汇编指令;Interlocked
有时确实使用锁:多处理器系统上的硬件锁;在单处理器系统中,没有硬件锁;Interlocked
不同之处volatile
在于它使用了一个完整的栅栏,而 volatile 使用了一个半栅栏。- 当您使用
volatile
. 它不能发生在Interlocked
.VolatileRead
并VolatileWrite
具有与 `volatile 相同的重新排序问题(感谢 Brian Gideon 的链接)。
现在我们有了规则,我们可以为您的问题定义一个答案:
- 从技术上讲:是的,有些事情你可以做而
volatile
你不能做Interlocked
:- 语法:你不能写
a = b
wherea
或b
is volatile,但这很明显; - 由于重新排序,您可以在将其写入 volatile 变量后读取不同的值。你不能用
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# 编程语言标准:
对于非易失性字段,考虑重新排序指令的优化技术可能会在多线程程序中导致意外和不可预知的结果,这些程序在没有同步的情况下访问字段,例如锁语句提供的那些。这些优化可以由编译器、运行时系统或硬件来执行。对于 volatile 字段,此类重新排序优化受到限制:
对 volatile 字段的读取称为volatile read。易失性读取具有 :acquire 语义”;也就是说,它保证在指令序列中对内存的任何引用之前发生。
对 volatile 字段的写入称为volatile write。易失性写入具有“释放语义”;也就是说,它保证发生在指令序列中写指令之前的任何内存引用之后。
更新:问题在很大程度上被重写,更正了我原来的回答并添加了一个“真实”的答案
这是一个相当复杂的话题。我发现 Joseph Albahari 的文章是.NET Framework 中多线程概念的更明确和准确的来源之一,可能有助于回答您的问题。
但是,为了快速总结,就如何使用它们而言,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 变量而不是锁来获得一些性能。
Lock 是一个完整的内存屏障,它可以为您提供与 volatile 变量以及许多其他变量相同的特性。正如已经说过的那样,volatile 只是确保在多线程场景中,如果 CPU 更改其缓存行中的值,其他 CPU 会立即看到该值,但根本不确保任何锁定语义。
问题是 lock 比 volatile 强大得多,你应该尽可能使用 volatile 来避免不必要的锁。