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

发布于 2024-07-05 08:03:17 字数 247 浏览 9 评论 0原文

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

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

这是我没能实现的。 即使尝试多次,启用优化等,我总是能在没有“易失性”关键字的情况下得到正确的行为。

您对这个话题有什么想法吗? 您知道如何在简单的演示应用程序中模拟此类问题吗? 这取决于硬件吗?

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

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

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.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

寻梦旅人 2024-07-12 08:03:17

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

主要思想来自 wiki,但对 C# 进行了一些更改。 wiki 文章演示了 C++ 静态字段的这一点,看起来 C# 总是仔细编译对静态字段的请求...并且我用非静态字段制作示例:

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

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");
    }
}

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:

If you run this example in Release mode 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 volatile keyword, you always get 'OK'.

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");
    }
}
戏蝶舞 2024-07-12 08:03:17

在 C# 中很难进行演示,因为代码是由虚拟机抽象的,因此在这台机器的一种实现上它可以正常工作而无需易失性,而在另一种实现上可能会失败。

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

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

另一个例子是忙等待。

同样,这种情况也可能发生在 C# 中,但这很大程度上取决于在虚拟机和 JIT 编译器上(或解释器,如果它没有 JIT...理论上,我认为 MS 总是使用 JIT 编译器,Mono 也使用一个编译器;但您也许可以手动禁用它)。

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.

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

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.

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).

假装不在乎 2024-07-12 08:03:17

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

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.

原谅我要高飞 2024-07-12 08:03:17

当未指定“易失性”关键字时,这实际上并不是发生错误的问题,而是在未指定关键字时可能会发生错误的问题。 一般来说,你会比编译器更清楚地知道什么时候发生这种情况!

最简单的思考方式是,如果编译器愿意,它可以内联某些值。 通过将值标记为易失性,您可以告诉自己和编译器该值实际上可能会发生变化(即使编译器不这么认为)。 这意味着编译器不应内联值、保留缓存或提前读取值(试图优化)。

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

MSDN 此处有一个简短的描述。
这可能是一篇关于波动性、原子性和联锁<的主题的更深入的文章< /a>

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!

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).

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

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

千里故人稀 2024-07-12 08:03:17

这是我对这种行为的集体理解的贡献...这并不多,只是一个演示(基于 xkip 的演示),它展示了易失性与非易失性(即“正常”)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();
    }

  }
}

上面有一些非常优秀的简洁解释,以及一些很棒的参考资料。 感谢大家帮助我了解 易失性(至少足以知道不要依赖于易失性,而我的第一直觉是锁定它) 。

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


PS:我对原始请求的演示非常感兴趣,该演示是:“我想看到一个静态易失性整数static int 行为不当的地方,

我已经尝试过但失败了(实际上我很快就放弃了;-)。行为“正确”,无论它们是否易变...并且我希望解释为什么会出现这种情况,如果确实如此...是不是编译器不会在寄存器中缓存静态变量的(即它缓存对堆地址的引用)?

不,这不是一个新问题。这是试图引导社区回到最初的问题。

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.

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 volatile where my first instinct was lock it).

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 static volatile int behaving correctly where a static int misbehaves.

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 values of static vars in registers (i.e. it caches a reference to that heap-address instead)?

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

浮华 2024-07-12 08:03:17

我看到 Joe Albahari 写的以下文字,对我帮助很大。

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

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
    }
}

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

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

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
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文