【问题标题】:Is there any advantage of using volatile keyword in contrast to use the Interlocked class?与使用 Interlocked 类相比,使用 volatile 关键字有什么优势吗?
【发布时间】:2010-12-14 15:30:09
【问题描述】:

换句话说,我可以对一个普通变量和 Interlocked 类也无法解决的 volatile 变量做一些事情吗?

【问题讨论】:

  • 我知道 volatile 有什么好处。问题是是否存在不能用互锁方法替换 volatile 的情况?

标签: c# .net multithreading volatile interlocked


【解决方案1】:

是的 - 您可以直接查看值。

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

执行此循环:

bool done = false;

...

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

如果您在另一个线程中将done 设置为true,您会期望循环退出。但是 - 如果 done 没有标记为 volatile,那么编译器可以选择意识到循环代码永远不会更改 done,并且它可以优化退出比较。

这是多线程编程的难点之一——许多问题只会在特定情况下出现。

【讨论】:

  • 你可以用 Interlocked.Read() 做同样的事情。
  • 您对 volatile 的描述略有错误。您描述了 c++ volatile(对多线程没有好处)。 C#(和 Java) volatile 使编译器在访问变量时插入同步操作。因为syncs,每次都强制重新读取变量,效果类似于c++的volatile。
【解决方案2】:

编辑:很大程度上重写了问题

为了回答这个问题,我对此事进行了深入研究,发现了一些我不知道的关于 volatileInterlocked 的事情。让我们澄清这一点,不仅是为了我,也是为了这个讨论和其他阅读这个的人:

  • volatile 读/写应该不受重新排序的影响。这表示读和写,确实表示任何其他动作;
  • volatility 不是强制在 CPU 上的,即硬件级别(x86 在 any 读/写时使用获取和释放栅栏)。它确实会阻止编译器或 CLR 优化;
  • Interlocked 对 CompareExchange (cmpxchg)、Increment (inc) 等使用原子汇编指令;
  • Interlocked 确实使用锁有时:hardware lock on multi processor systems;在单处理器系统中,没有硬件锁;
  • Interlockedvolatile 的不同之处在于它使用了全栅栏,而 volatile 使用了半栅栏。
  • A read following a write can be reordered 当您使用 volatile 时。 Interlocked 不会发生这种情况。 VolatileReadVolatileWrite 与 `volatile 有相同的重新排序问题(链接感谢 Brian Gideon)。

既然我们有了规则,我们就可以为您的问题定义一个答案:

  • 技术上:是的,有些事情你可以用volatile 做而你不能用Interlocked 做:
    1. 语法:你不能写a = b,其中ab是易失性的,但这很明显;
    2. 由于重新排序,您可以在将其写入 volatile 变量后读取不同的值。 Interlocked 无法做到这一点。换句话说:使用volatile,您可以更少安全,然后使用Interlocked
    3. 性能:volatileInterlocked 更快。
  • 语义上:不,因为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 语义”;也就是说,它 保证发生在任何之前 对之后发生的内存的引用 它在指令序列中。

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

更新:问题在很大程度上被重写,更正了我原来的回答并添加了一个“真实”的答案

【讨论】:

  • Interlocked 类不必对锁做太多事情,并且(与 lock 相比)分配给它的开销并不多 - 在大多数 CPU 上,Interlocked 方法是作为单条指令实现的。
  • 你说得对,谢谢指点。查看CompareExchange(认为它不是原子的)SSCLI(共享源CLI)表明它最终会转换为x86处理器上的cmpxchg汇编指令,并且该指令——就像Interlocked中的任何其他指令一样——确实原子的。
  • 那个,以及其他更正,加上重写现在都在答案中:)
  • 你,你不能强制线程安全访问没有 volatile 关键字的变量。但是您可以在其 getter/setter 中定义一个使用 Interlocked.Read() 和 Interlocked.Exchange() 的属性。
  • 这是在伪装一个变量。你当然可以这样做。但这并没有改变您不能以这种方式拥有 variablefield 的事实。隐藏在属性后面使该字段不易出错,但在类内部和外部(通过反射打破 OO),它只是可访问的,具有线程不安全和所有。 OTOH,不可能将“易失性”属性的地址提供给某些外部函数(API,硬件,否则),您需要提交该字段的地址,然后 Interlocked 无能为力。
【解决方案3】:

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

但是,快速总结一下,volatile 关键字和 Interlocked 类之间在如何使用方面存在很多重叠。当然,两者都远远超出了普通变量所能做的事情。

【讨论】:

  • 非常棒的文章!阅读它需要一段时间,而完全理解它需要更长的时间
【解决方案4】:

我不会试图成为这个主题的权威,但我强烈建议您看看自吹自擂的 Jon Skeet 的 this article

还可以查看this answer 的最后部分,其中详细说明了volatile 的用途。

【讨论】:

  • Skeet 的文章不错,但只涵盖了基础知识。我知道 volatile 有什么好处,但问题是是否存在无法用联锁方法替换 volatile 的情况?
【解决方案5】:

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

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

问题是锁比 volatile 更强大,你应该尽可能使用 volatile 来避免不必要的锁。

【讨论】:

  • 我说的不是 lock 语句,而是 Interlocked 类,但是你说的是真的
猜你喜欢
  • 2015-07-20
  • 2018-10-08
  • 2012-12-09
  • 1970-01-01
  • 2019-12-13
  • 2010-09-24
  • 2010-12-27
  • 2015-04-06
  • 2013-05-09
相关资源
最近更新 更多