说明 C# 中 volatile 关键字的用法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/133270/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 14:58:11  来源:igfitidea点击:

Illustrating usage of the volatile keyword in C#

提问by Romain Verdier

I would like to code a little program which visually illustrates the behavior of the volatilekeyword. Ideally, it should be a program which performs concurrent access to a non volatile static field and which gets incorrect behavior because of that.

我想编写一个小程序来直观地说明volatile关键字的行为。理想情况下,它应该是一个程序,它执行对非易失性静态字段的并发访问,并因此得到不正确的行为。

Adding the volatile keyword in the same program should fix the problem.

在同一个程序中添加 volatile 关键字应该可以解决问题。

That something I didn't manage to achieve. Even trying several times, enabling optimization, etc., I always get a correct behavior without the 'volatile' keyword.

那是我没能做到的。即使尝试多次,启用优化等,我也总是在没有 'volatile' 关键字的情况下得到正确的行为。

Do you have any idea about this topic? Do you know how to simulate such a problem in a simple demo app? Does it depend on hardware?

你对这个话题有什么想法吗?你知道如何在一个简单的演示应用程序中模拟这样的问题吗?它依赖于硬件吗?

采纳答案by Mecki

I've achieved a working example!

我已经实现了一个工作示例!

The main idea received from wiki, but with some changes for C#. The wiki article demonstrates this for static field of C++, it is looks like C# always carefully compile requests to static fields... and i make example with non static one:

主要思想来自 wiki,但对 C# 进行了一些更改。维基文章为 C++ 的静态字段演示了这一点,看起来 C# 总是小心翼翼地编译对静态字段的请求......我用非静态字段举例:

If you run this example in Releasemode and without debugger(i.e. using Ctrl+F5) then the line while (test.foo != 255)will be optimized to 'while(true)' and this program never returns. But after adding volatilekeyword, you always get 'OK'.

如果您在发布模式下运行此示例并且没有调试器(即使用 Ctrl+F5),那么该行将while (test.foo != 255)被优化为 'while(true)' 并且该程序永远不会返回。但是添加volatile关键字后,您总是会得到“确定”。

class Test
{
    /*volatile*/ int foo;

    static void Main()
    {
        var test = new Test();

        new Thread(delegate() { Thread.Sleep(500); test.foo = 255; }).Start();

        while (test.foo != 255) ;
        Console.WriteLine("OK");
    }
}

回答by Craig Stuntz

Yes, it's hardware dependent (you are unlikely to see the problem without multiple processors), but it's also implementation dependent. The memory model specifications in the CLR spec permit things which the Microsoft implementation of the CLR do not necessarily do.

是的,它依赖于硬件(如果没有多个处理器,您不太可能看到问题),但它也依赖于实现。CLR 规范中的内存模型规范允许 CLR 的 Microsoft 实现不一定做的事情。

回答by Ray Hayes

It's not really a matter of a fault happening when the 'volatile' keyword isn't specified, more that an error could happen when it hasn't been specified. Generally you are going to know when this is the case better than the compiler!

当未指定 'volatile' 关键字时,这并不是真正的故障问题,而是在未指定时可能会发生错误。通常,您会比编译器更了解这种情况!

The easiest way of thinking about it would be that the compiler could, if it wanted to, inline certain values. By marking the value as volatile, you are telling yourself and the compiler that the value may actually change (even if the compiler doesn't think so). This means the compiler should not in-line values, keep cache or read the value early (in an attempt to optimize).

考虑它的最简单方法是编译器可以,如果它愿意,内联某些值。通过将值标记为 volatile,您是在告诉自己和编译器该值实际上可能会更改(即使编译器不这么认为)。这意味着编译器不应内联值、保留缓存或尽早读取值(以尝试优化)。

This behaviour isn't really the same keyword as in C++.

这种行为实际上与 C++ 中的关键字不同。

MSDN has a short description here. Here is a perhaps a more in depth post on the subjects of Volatility, Atomicity and Interlocking

MSDN这里有一个简短的描述。这是一篇关于波动性、原子性和互锁主题的更深入的帖子

回答by Mecki

It's hard to demonstrate in C#, as the code is abstracted by a virtual machine, thus on one implementation of this machine it work right without volatile, while it might fail on another one.

很难在 C# 中演示,因为代码是由虚拟机抽象出来的,因此在这台机器的一个实现上它可以正常工作而没有 volatile,而在另一台机器上它可能会失败。

The Wikipedia has a good example how to demonstrate it in C, though.

不过,维基百科有一个很好的例子如何用 C 来演示它。

The same thing could happen in C# if the JIT compiler decides that the value of the variable cannot change anyway and thus creates machine code that doesn't even check it any longer. If now another thread was changing the value, your first thread might still be caught in the loop.

如果 JIT 编译器决定变量的值无论如何都不能改变,从而创建甚至不再检查它的机器代码,那么在 C# 中也会发生同样的事情。如果现在另一个线程正在更改该值,则您的第一个线程可能仍会在循环中被捕获。

Another example is Busy Waiting.

另一个例子是忙等待。

Again, this could happen with C# as well, but it strongly depends on the virtual machine and on the JIT compiler (or interpreter, if it has no JIT... in theory, I think MS always uses a JIT compiler and also Mono uses one; but you might be able to disable it manually).

同样,这也可能发生在 C# 中,但它强烈依赖于虚拟机和 JIT 编译器(或解释器,如果它没有 JIT...理论上,我认为 MS 总是使用 JIT 编译器,Mono 也使用一;但您可以手动禁用它)。

回答by corlettk

Here's my contribution to the collective understanding of this behaviour... It's not much, just a demonstration (based on xkip's demo) which shows the behaviour of a volatile verses a non-volatile (i.e. "normal") int value, side-by-side, in the same program... which is what I was looking for when I found this thread.

这是我对这种行为的集体理解的贡献......它并不多,只是一个演示(基于 xkip 的演示),它显示了 volatile 与非 volatile(即“正常”)int 值的行为,并排-side,在同一个程序中......这就是我找到这个线程时正在寻找的。

using System;
using System.Threading;

namespace VolatileTest
{
  class VolatileTest 
  {
    private volatile int _volatileInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _volatileInt = 1; }).Start();
      while ( _volatileInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_volatileInt="+_volatileInt);
    }
  }

  class NormalTest 
  {
    private int _normalInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _normalInt = 1; }).Start();
      // NOTE: Program hangs here in Release mode only (not Debug mode).
      // See: http://stackoverflow.com/questions/133270/illustrating-usage-of-the-volatile-keyword-in-c-sharp
      // for an explanation of why. The short answer is because the
      // compiler optimisation caches _normalInt on a register, so
      // it never re-reads the value of the _normalInt variable, so
      // it never sees the modified value. Ergo: while ( true )!!!!
      while ( _normalInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_normalInt="+_normalInt);
    }
  }

  class Program
  {
    static void Main() {
#if DEBUG
      Console.WriteLine("You must run this program in Release mode to reproduce the problem!");
#endif
      new VolatileTest().Run();
      Console.WriteLine("This program will now hang!");
      new NormalTest().Run();
    }

  }
}

There are some really excellent succinct explanations above, as well as some great references. Thanks to all for helping me get my head around volatile(atleast enough to know not rely to on volatilewhere my first instinct was lockit).

上面有一些非常出色的简洁解释,以及一些很好的参考资料。感谢所有帮助我解决问题的人volatile(至少足以知道不要依赖于volatile我的第一直觉在哪里lock)。

Cheers, and Thanks for ALL the fish. Keith.

干杯,感谢所有的鱼。基思。



PS:I'd be very interested in a demo of the original request, which was: "I'd like to see a staticvolatile intbehaving correctly where a staticintmisbehaves.

PS:“我想看到的:我会很在原请求的演示,这是有意一个静态挥发INT正常运作,其中一个静态INT行为不端。

I have tried and failed this challenge. (Actually I gave up pretty quickly ;-). In everything I tried with static vars they behave "correctly" regardless of whether or not they're volatile... and I'd love an explanation of WHY that is the case, if indeed it is the case... Is it that the compiler doesn't cache the valuesof static vars in registers (i.e. it caches a reference tothat heap-address instead)?

我已经尝试并失败了这个挑战。(实际上我很快就放弃了;-)。在我尝试使用静态变量的所有内容中,无论它们是否不稳定,它们的行为都是“正确的” ......我很想解释为什么会这样,如果确实如此......是这样吗?编译器不会将静态变量的缓存在寄存器中(即,它会缓存对该堆地址的引用)?

No this isn't a new question... it's an attempt to stear the community backto the original question.

不,这不是一个新问题……它试图让社区回到最初的问题。

回答by Martijn B

I came across the following text by Joe Albahari that helped me a lot.

我遇到了乔·阿尔巴哈里 (Joe Albahari) 的以下文字,对我帮助很大。

I grabbed an example from the above text which I altered a little bit, by creating a static volatile field. When you remove the volatilekeyword the program will block indefinitely. Run this example in Releasemode.

我从上面的文本中抓取了一个示例,通过创建一个静态 volatile 字段,我对其进行了一些修改。当您删除volatile关键字时,程序将无限期阻止。在发布模式下运行此示例。

class Program
{
    public static volatile bool complete = false;

    private static void Main()
    {           
        var t = new Thread(() =>
        {
            bool toggle = false;
            while (!complete) toggle = !toggle;
        });

        t.Start();
        Thread.Sleep(1000); //let the other thread spin up
        complete = true;
        t.Join(); // Blocks indefinitely when you remove volatile
    }
}