Joe Albahari在多线程上有一个great series,这是必须阅读的,对于任何进行C#多线程处理的人来说都应该被人们所熟知。
在第4部分中,他提到了挥发性问题:
请注意,应用volatile不会阻止写入后跟a 从交换中读取,这可以创建脑筋急转弯。乔达菲 通过以下示例很好地说明了问题:如果Test1和 Test2在不同的线程上同时运行,它可以用于和 b最终得到的值为0(尽管使用了volatile 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供应商?
答案 0 :(得分:31)
尽管流行的博客帖子声称有这样的事情,但其当前实施中的易失性并非被破坏。然而,它被严格指定,并且在字段上使用修饰符来指定内存排序的想法并不是那么好(将Java / C#中的volatile与C ++的原子规范进行比较,这些规范有足够的时间从早期学习错误)。另一方面,MSDN文章显然是由一个没有业务谈论并发性的人写的,并且完全是假的。唯一合理的选择是完全忽略它。
Volatile在访问字段时保证获取/释放语义,并且只能应用于允许原子读写的类型。不是更多,而不是更少。这足以有效地实现许多无锁算法,例如non-blocking hashmaps。
一个非常简单的示例是使用volatile变量来发布数据。感谢x上的volatile,下面代码片段中的断言无法触发:
private int a;
private volatile bool x;
public void Publish()
{
a = 1;
x = true;
}
public void Read()
{
if (x)
{
// if we observe x == true, we will always see the preceding write to a
Debug.Assert(a == 1);
}
}
易失性并不容易使用,在大多数情况下,你最好采用更高级别的概念,但是当性能很重要或者你正在实现某些低级数据结构时,volatile可能非常有用。
答案 1 :(得分:13)
当我阅读MSDN文档时,我相信它说如果你在变量上看到volatile,你不必担心编译器优化会搞砸价值,因为它们会重新排序操作。它并不表示您受到保护,不会因您自己的代码以错误的顺序在不同的线程上执行操作而导致错误。 (虽然不可否认,评论并不清楚。)
答案 2 :(得分:0)
以下是 C# 中 volatile 的一些有用反汇编:https://sharplab.io/#gist:625b1181356b543157780baf860c9173
在 x86 上大概是:
当我只想告诉编译器一个字段可能会从许多不同的线程中更新并且我不需要由互锁操作提供的附加功能时,我会使用 volatile。