C#中的'volatile'关键字是否仍然存在?

dra*_*707 31 c# multithreading volatile

Joe Albahari有一个关于多线程的伟大系列,这是一本必须阅读的内容,对于任何进行C#多线程处理的人来说都应该被人们所熟知.

然而,在第4部分中,他提到了易变的问题:

请注意,应用volatile不会阻止写入后读取交换,这可以创建脑筋急转弯.Joe Duffy通过以下示例很好地说明了这个问题:如果Test1和Test2在不同的线程上同时运行,则a和b最终都可能为0(尽管在x和y上都使用了volatile)

接下来是MSDN文档不正确的说明:

MSDN文档指出使用volatile关键字可确保始终在字段中显示最新值.这是不正确的,因为正如我们所见,可以重新排序读取后跟读取.

我查看了MSDN文档,该文档最后一次在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; }
}
Run Code Online (Sandbox Code Playgroud)

关于多线程的部分是在2011年写的,这个论点今天仍然有效吗?是否应该不惜一切代价避免使用volatile或完全内存防护,以防止引入非常难以产生的错误,如上所述甚至依赖于它运行的CPU供应商?

Voo*_*Voo 32

尽管流行的博客帖子声称有这样的事情,但其当前实施中的易变性并未被打破.然而,它被严格指定,并且在字段上使用修饰符来指定内存排序的想法并不那么好(将Java/C#中的volatile与C++的原子规范进行比较,这些规范有足够的时间来学习早期的错误).另一方面,MSDN文章清楚地写了一个没有业务谈论并发性并且完全是虚假的人.唯一合理的选择是完全忽略它.

Volatile 在访问字段时保证获取/释放语义,并且只能应用于允许原子读取和写入的类型.不是更多,而不是更少.这足以有效地实现许多无锁算法,例如非阻塞散列图.

一个非常简单的示例是使用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); 
    }
}
Run Code Online (Sandbox Code Playgroud)

易失性并不容易使用,在大多数情况下,你最好采用更高级别的概念,但是当性能很重要或者你正在实现一些低级数据结构时,volatile可能非常有用.

  • @Mehrdad C#规范确实保证了获取/发布,并且**不保证顺序一致性.(规范第10.5.3节).顺序一致性是一种昂贵的保证,因此他们不提供它. (4认同)
  • 您是否打算将"a"变为易失性,或者"x"上的波动是否确保写入"a"发生了? (3认同)
  • @Patrick代码是正确的.内存排序保证比"写入无法缓存"更严格,这也在这里播放.为了大大简化:如果线程B看到对volatile变量X的更新,则保证在将值写入x时在线程A上看到之前发生的所有写入.这允许我们使用单个volatile bool来发布其他数据. (2认同)

Cha*_*ana 13

当我阅读MSDN文档时,我相信它说如果你看到变量上的volatile,你不必担心编译器优化会搞砸价值,因为它们会重新排序操作.它并不表示您受到保护,不会因您自己的代码以错误的顺序在不同的线程上执行操作而导致错误.(虽然不可否认,评论并不清楚.)

  • 我同意.我认为重点应该是"声明为volatile的字段不受**编译器优化的影响**,它假定单个线程访问.这确保了字段中始终存在最新值. " (6认同)
  • @ MikeSherrill'CatRecall':"编译器"是一个红鲱鱼.线程安全是一个远远超出编译器的问题.例如,在CPU中重新排序也同样糟糕. (4认同)
  • 每个编译器都必须了解CPU重新排序规则.如果指令A和B可以由CPU重新排序,但语言语义另有规定,那么编译器**必须**引入某种类型的栅栏来阻止重新排序,或者选择替代指令来实现相同的目标.因此,在`volatile`语义推进重新排序的地方,编译器不仅要避免重新排序,还要阻止CPU这样做. (3认同)