【发布时间】:2016-12-02 18:27:01
【问题描述】:
Joe Albahari 有一篇关于多线程的 great series,这是一本必读的文章,任何从事 C# 多线程的人都应该牢记于心。
然而,在第 4 部分中,他提到了 volatile 的问题:
请注意,应用 volatile 不会阻止写入后跟 从被交换中读取,这可以创建脑筋急转弯。乔·达菲 下面的例子很好地说明了这个问题:如果 Test1 和 Test2 在不同的线程上同时运行,有可能是 a 和 b 都以 0 的值结束(尽管使用了 volatile on x 和 y)
随后是 MSDN 文档不正确的说明:
MSDN 文档指出使用 volatile 关键字可确保 字段中始终存在最新的值。 这是不正确的,因为正如我们所见,写后读可以 重新排序。
我检查了MSDN documentation,它最后一次更改是在 2015 年,但仍然列出:
volatile 关键字表示一个字段可能被 同时执行的多个线程。是的字段 声明的 volatile 不受编译器优化的影响 假设由单个线程访问。 这样可以确保最 字段中始终存在最新值。
现在我仍然避免使用 volatile,而是使用更详细的方法来防止线程使用过时的数据:
private int foo;
private object fooLock = new object();
public int Foo {
get { lock(fooLock) return foo; }
set { lock(fooLock) foo = value; }
}
由于有关多线程的部分是在 2011 年编写的,那么这个论点今天仍然有效吗?是否仍应不惜一切代价避免 volatile 以支持锁定或完整的内存栅栏,以防止引入非常难以产生的错误,如上所述甚至依赖于运行它的 CPU 供应商?
【问题讨论】:
-
在您的示例中,围绕
return和lock的赋值语句有什么意义? -
这仍然具有误导性。 Volatile 提供了获取/释放内存语义,足以有效地实现许多算法。是的,它很难使用,但这并没有破坏它(c++ 的解决方案显然更优越,但他们的优势在于看到了 java 中 volatile 的问题)(并且任何认为内存屏障比 volatile 更容易的人都没有足够的具有 x86 以外架构的经验。尝试在没有多副本原子性的架构上使用内存屏障,看看你能走多远)
-
(msdn 的描述显然更糟。显然写 sn-p 的人根本不懂 volatile,只能希望永远不允许编写一行并发代码)
-
@Yarik 锁提供完整的内存防护前后。内存栅栏确保 CPU 缓存从内存中刷新。例如,我在
while(!Stopped)中使用它来在单独的线程上进行后台服务工作。如果 Windows 服务停止,我将 Stopped 设置为 true,以便单独的线程跳出循环。如果没有锁定(因此围栏),属性的支持字段可能会一直缓存在 CPU 中,永远不会刷新,线程也永远不会停止。 -
目前你的锁实际上并没有比
volatile做更多的事情,而且无论如何不能防止许多类的同步问题。其中大部分与两个线程的交错有关 - 例如,尝试同时从两个线程执行类似foo++的操作可能仍然具有未定义的结果,并且鉴于您的锁定示例,为什么应该更明显:每个加载和存储都有一个单独的锁,这意味着两个线程必须为每个操作谁先做斗争(这是否与您当前使用它的代码相关,目前尚不清楚)。
标签: c# multithreading volatile