使用volatile关键字与使用Interlocked类相比有什么优势吗?

换句话说,我可以使用常规变量和Interlocked类无法解决的volatile变量吗?

编辑:问题很大程度上改写

为了回答这个问题,我在这个问题上进一步深入研究,发现了一些我不知道的关于volatileInterlocked事情。 让我们清楚地了解这一点,不仅仅是为了我,而是为了这次讨论和其他人阅读:

  • volatile读/写应该不受重新排序的影响。 这只意味着阅读和写作,并不意味着任何其他行动;
  • 不强制在CPU上进行波动 ,即硬件级别(x86在任何读/写时使用获取和释放围栏)。 它确实阻止了编译器或CLR优化;
  • Interlocked使用primefaces汇编指令进行CompareExchange( cmpxchg ),Increment( inc )等;
  • Interlocked有时会使用锁定: 多处理器系统上的硬件锁定 ; 在单处理器系统中,没有硬件锁;
  • Interlocked不同于volatile ,因为它使用完整的栅栏 ,其中volatile使用半栅栏。
  • 使用volatile时, 可以重新排序写入后的读取 。 Interlocked不可能发生这种情况。 VolatileReadVolatileWrite具有与VolatileWrite相同的重新排序问题(链接感谢Brian Gideon)。

现在我们有了规则,我们可以为您的问题定义答案:

  • 从技术上讲:是的,有些事情你可以用volatile来做,你不能用Interlocked做:
    1. 语法:你不能写a = b ,其中ab是volatile,但这很明显;
    2. 由于重新排序,您可以在将其写入volatile变量后读取不同的值。 你不能用Interlocked做到这一点。 换句话说:你可以用volatile安全,然后你就可以使用Interlocked
    3. 性能: volatileInterlocked快。
  • 语义上:不,因为Interlocked只提供操作的超集,并且使用起来更安全,因为它应用了完整的防护。 对于使用Interlocked无法做到的volatile你不能做任何事情,你可以用Interlocked很多事情 ,你不能用volatile做:

     static volatile int x = 0; x++; // non-atomic static int y = 0; Interlocked.Increment(y); // atomic 
  • 范围:是的,声明变量volatile会使每次访问都变得不稳定。 不可能以任何其他方式强制执行此行为,因此不能用Interlocked替换volatile 。 在其他库,接口或硬件可以访问您的变量并随时更新它或需要最新版本的情况下,这是必需的。

如果您问我,最后一点是对volatile的实际需求,并且可能使其成为两个进程共享内存并且无需锁定即可读取或写入的理想选择。 在此上下文中将变量声明为volatile是更安全的,然后强制所有程序员使用Interlocked (编译器无法强制使用)。


编辑:以下引用是我原来答案的一部分,我会把它留在;-)

引用C#编程语言标准:

对于非易失性字段,考虑重新排序指令的优化技术可能导致multithreading程序中出现意外和不可预测的结果,这些程序访问没有同步的字段,例如lock-statement提供的字段。 这些优化可以由编译器,运行时系统或硬件执行。 对于易失性字段,此类重新排序优化受到限制:

  • 读取volatile字段称为volatile读取 。 易失性读取具有:获取语义“;也就是说,它保证在对指令序列之后的内存的任何引用之前发生。

  • 写入易失性字段称为易失性写入 。 易失性写入具有“释放语义”; 也就是说,保证在指令序列中的写指令之前的任何存储器引用之后发生。

更新:大部分重写的问题,纠正了我的原始回复并添加了“真实”的答案

这是一个相当复杂的话题。 我发现Joseph Albahari的文章是.NET Framework中multithreading概念的更明确和准确的来源之一,可能有助于回答您的问题。

但是,为了快速总结, volatile关键字和Interlocked类之间有很多重叠,只要它们如何使用。 当然,这两种方法都超出了普通变量的范围。

是的 – 您可以直接查看该值。

只要你只使用Interlocked类来访问变量,那就没有区别了。 volatile的作用是告诉编译器该变量是特殊的,并且在优化它时不应该假设该值没有改变。

采取这个循环:

 bool done = false; ... while(!done) { ... do stuff which the compiler can prove doesn't touch done... } 

如果你在另一个线程中将done设置为true ,你会期望循环退出。 但是 – 如果完成没有标记为volatile那么编译器可以选择实现循环代码永远不会改变,并且可以优化退出比较。

这是multithreading编程的难点之一 – 许多问题的情况只在某些情况下出现。

我不会试图成为这个主题的权威,但我强烈建议你由吹嘘的Jon Skeet看看这篇文章 。

另请参阅本答案的最后部分,详细说明应使用哪种volatile

是的,您可以通过使用volatile变量而不是锁来获得一些性能。

Lock是一个完整的内存屏障,它可以为您提供与volatile变量相同的特性以及许多其他变量。 正如已经说过的那样,volatile只是确保在multithreading场景中,如果CPU更改其缓存行中的值,其他CPU会立即看到该值,但根本不确保任何锁定语义。

事情是锁定比volatile更强大,你应该使用volatile来避免不必要的锁定。