简体   繁体   English

'volatile' 关键字在 C# 中仍然无效吗?

[英]Is the 'volatile' keyword still broken in C#?

Joe Albahari has a great series on multithreading that's a must read and should be known by heart for anyone doing C# multithreading.乔·阿尔巴哈里 (Joe Albahari) 有一个关于多线程的精彩系列,这是一本必读的文章,对于任何使用 C# 多线程的人来说,都应该牢记在心。

In part 4 however he mentions the problems with volatile:然而,在第 4 部分中,他提到了 volatile 的问题:

Notice that applying volatile doesn't prevent a write followed by a read from being swapped, and this can create brainteasers.请注意,应用 volatile 不会阻止写入后读取的交换,这可能会造成脑筋急转弯。 Joe Duffy illustrates the problem well with the following example: if Test1 and Test2 run simultaneously on different threads, it's possible for a and b to both end up with a value of 0 (despite the use of volatile on both x and y) Joe Duffy 用下面的例子很好地说明了这个问题:如果 Test1 和 Test2 同时在不同的线程上运行,a 和 b 的最终值可能都是 0(尽管在 x 和 y 上都使用了 volatile)

Followed by a note that the MSDN documentation is incorrect:后跟说明MSDN文档不正确:

The MSDN documentation states that use of the volatile keyword ensures that the most up-to-date value is present in the field at all times. MSDN 文档指出,使用 volatile 关键字可确保字段中始终存在最新值。 This is incorrect, since as we've seen, a write followed by a read can be reordered.这是不正确的,因为正如我们所看到的,写入后读取可以重新排序。

I've checked the MSDN documentation , which was last changed in 2015 but still lists:我检查了MSDN 文档,该文档最后一次更改是在 2015 年,但仍列出:

The volatile keyword indicates that a field might be modified by multiple threads that are executing at the same time. volatile 关键字表示一个字段可能会被同时执行的多个线程修改。 Fields that are declared volatile are not subject to compiler optimizations that assume access by a single thread.声明为 volatile 的字段不受假定由单个线程访问的编译器优化的影响。 This ensures that the most up-to-date value is present in the field at all times .这确保了该字段中始终存在最新的值

Right now I still avoid volatile in favor of the more verbose to prevent threads using stale data:现在我仍然避免使用 volatile 支持更详细的以防止线程使用陈旧数据:

private int foo;
private object fooLock = new object();
public int Foo {
    get { lock(fooLock) return foo; }
    set { lock(fooLock) foo = value; }
}

As the parts about multithreading were written in 2011, is the argument still valid today?由于关于多线程的部分是在 2011 年写的,这个论点今天仍然有效吗? Should volatile still be avoided at all costs in favor of locks or full memory fences to prevent introducing very hard to produce bugs that as mentioned are even dependent on the CPU vendor it's running on?是否应该不惜一切代价避免 volatile 以支持锁定或完整内存栅栏,以防止引入非常难以产生的错误,这些错误甚至依赖于它运行的 CPU 供应商?

Volatile in its current implementation is not broken despite popular blog posts claiming such a thing.尽管流行的博客文章声称有这样的事情,但其当前实现中的 Volatile 并没有被破坏。 It is however badly specified and the idea of using a modifier on a field to specify memory ordering is not that great (compare volatile in Java/C# to C++'s atomic specification that had enough time to learn from the earlier mistakes).然而,它的指定很糟糕,并且在字段上使用修饰符来指定内存排序的想法并不是那么好(将 Java/C# 中的 volatile 与 C++ 的原子规范进行比较,后者有足够的时间从早期的错误中吸取教训)。 The MSDN article on the other hand was clearly written by someone who has no business talking about concurrency and is completely bogus.. the only sane option is to completely ignore it.另一方面,MSDN 文章显然是由一个没有业务谈论并发并且完全是假的人写的。唯一明智的选择是完全忽略它。

Volatile guarantees acquire/release semantics when accessing the field and can only be applied to types that allow atomic reads and writes . Volatile 在访问字段时保证获取/释放语义,并且只能应用于允许原子读写的类型。 Not more, not less.不多也不少。 This is enough to be useful to implement many lock-free algorithms efficiently such as non-blocking hashmaps .这足以有效地实现许多无锁算法,例如非阻塞哈希图

One very simple sample is using a volatile variable to publish data.一个非常简单的示例是使用 volatile 变量来发布数据。 Thanks to the volatile on x, the assertion in the following snippet cannot fire:由于 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 is not easy to use and in most situations you are much better off to go with some higher level concept, but when performance is important or you're implementing some low level data structures, volatile can be exceedingly useful. Volatile 并不容易使用,在大多数情况下,您最好采用一些更高级别的概念,但是当性能很重要或您正在实现一些低级数据结构时, volatile 可能会非常有用。

As I read the MSDN documentation, I believe it is saying that if you see volatile on a variable, you do not have to worry about compiler optimizations screwing up the value because they reorder the operations.当我阅读 MSDN 文档时,我相信它是说如果您在变量上看到 volatile,您不必担心编译器优化会搞砸该值,因为它们会重新排序操作。 It doesn't say that you are protected from errors caused by your own code executing operations on separate threads in the wrong order.这并不是说您可以避免因您自己的代码以错误的顺序在不同线程上执行操作而导致的错误。 (although admittedly, the comment is not clear as to this.) (尽管无可否认,评论对此并不明确。)

volatile is a very limited guarantee. volatile是一个非常有限的保证。 It means that the variable isn't subject to compiler optimizations that assume access from a single thread.这意味着该变量不受假定从单个线程访问的编译器优化的影响。 This means that if you write into a variable from one thread, then read it from another thread, the other thread will definitely have the latest value.这意味着如果你从一个线程写入一个变量,然后从另一个线程读取它,另一个线程肯定会有最新的值。 Without volatile, one a multiprocessor machine without volatile, the compiler may make assumptions about single-threaded access, for example by keeping the value in a register, which prevents other processors from having access to the latest value.如果没有 volatile,一个没有 volatile 的多处理器机器,编译器可能会假设单线程访问,例如通过将值保存在寄存器中,这会阻止其他处理器访问最新值。

As the code example you've mentioned shows, it doesn't protect you from having methods in different blocks reordered.正如您提到的代码示例所示,它并不能保护您免于重新排序不同块中的方法。 In effect volatile makes each individual access to a volatile variable atomic.实际上, volatile使每个人都可以原子地访问volatile变量。 It doesn't make any guarantees as to the atomicity of groups of such accesses.它不保证此类访问组的原子性。

If you just want to ensure that your property has an up-to-date single value, you should be able to just use volatile .如果您只想确保您的属性具有最新的单一值,您应该可以只使用volatile

The problem comes in if you try to perform multiple parallel operations as if they were atomic.如果您尝试执行多个并行操作,就好像它们是原子操作一样,问题就会出现。 If you have to force several operations to be atomic together, you need to lock the whole operation.如果必须强制将多个操作原子化在一起,则需要锁定整个操作。 Consider the example again, but using locks:再次考虑这个例子,但使用锁:

class DoLocksReallySaveYouHere
{
  int x, y;
  object xlock = new object(), ylock = new object();

  void Test1()        // Executed on one thread
  {
    lock(xlock) {x = 1;}
    lock(ylock) {int a = y;}
    ...
  }

  void Test2()        // Executed on another thread
  {
    lock(ylock) {y = 1;}
    lock(xlock) {int b = x;}
    ...
  }
}

The locks cause may cause some synchronization, which may prevent both a and b from having value 0 (I have not tested this).锁原因可能会导致一些同步,这可以防止两者ab由具有值0(I没有测试此)。 However, since both x and y are locked independently, either a or b can still non-deterministically end up with a value of 0.但是,由于xy都是独立锁定的,因此ab仍然可以不确定地以值 0 结束。

So in the case of wrapping the modification of a single variable, you should be safe using volatile , and would not really be any safer using lock .因此,在包装单个变量的修改的情况下,使用volatile应该是安全的,而使用lock并不会真正更安全。 If you need to atomically perform multiple operations, you need to use a lock around the entire atomic block, otherwise scheduling will still cause non-deterministic behavior.如果需要原子地执行多个操作,则需要在整个原子块周围使用一个lock ,否则调度仍然会导致非确定性行为。

Here are some useful disassemblies for volatile in C#: https://sharplab.io/#gist:625b1181356b543157780baf860c9173以下是 C# 中 volatile 的一些有用反汇编: https : //sharplab.io/#gist : 625b1181356b543157780baf860c9173

On x86 it is just about:在 x86 上,它大约是:

  • using memory instead of registers使用内存代替寄存器
  • preventing compiler optimizations like in the case with the endless loop防止编译器优化,就像在无限循环的情况下一样

I use volatile when I just want to tell compiler that a field might be updated from many different threads and I do not need additional features provided by interlocked operations.当我只想告诉编译器一个字段可能从许多不同的线程更新并且我不需要由互锁操作提供的附加功能时,我使用 volatile。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM