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

Posted

技术标签:

【中文标题】说明 C# 中 volatile 关键字的用法【英文标题】:Illustrating usage of the volatile keyword in C# 【发布时间】:2008-09-25 13:20:03 【问题描述】:

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

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

那是我没能做到的。即使尝试了几次,启用优化等,我总是得到正确的行为,而没有'volatile'关键字。

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

【问题讨论】:

【参考方案1】:

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

主要思想来自 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");
    

【讨论】:

在 x86 和 x64 的 .NET 4.0 上测试 - 可以确认该示例仍然适用。谢谢! :) 太好了!我从来不知道 JIT 会进行这种优化,而 volatile 会禁用它。 明确地说,您是在 foo 的声明中添加“volatile”关键字,对吧? 示例有效。我确信我遇到的问题(线程卡在循环中)与波动性(或缺乏波动性)有关,但即使在人为地创建场景之后也无法重现,直到我打开优化。将 'volatile' 放在 bool 上解决了无限循环问题,但现在有人担心代码中的其他地方会这样做,因此决定关闭优化。我对此感到有点惊讶,或者只是打开优化(最近才完成)可能会咬人。【参考方案2】:

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

【讨论】:

【参考方案3】:

当没有指定'volatile'关键字时,这并不是真正的错误问题,而是在没有指定时可能会发生错误。一般来说,你会比编译器更好地知道这种情况!

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

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

MSDN 有一个简短的描述here。 这是一篇关于Volatility, Atomicity and Interlocking主题的更深入的帖子

【讨论】:

【参考方案4】:

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

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

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

Another example is Busy Waiting.

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

【讨论】:

【参考方案5】:

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

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://***.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();
    

  

上面有一些非常简洁的解释,以及一些很好的参考资料。感谢所有人帮助我了解volatile(至少知道不要依赖volatile,我的第一直觉是lock它)。

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


PS:我对原始请求的演示非常感兴趣,它是:“我希望看到 a static volatile int static int 行为不端的地方表现正确。

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

不,这不是一个新问题...这是试图让社区回到原来的问题。

【讨论】:

【参考方案6】:

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

Memory Barriers and Volatility

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

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
    

【讨论】:

以上是关于说明 C# 中 volatile 关键字的用法的主要内容,如果未能解决你的问题,请参考以下文章

从JAVA看C#中volatile和synchronized关键字的作用

volatile关键字用法

C语言关键词volatile的用法

java里volatile关键字有啥特性?

java关键字 volatile的作用及使用说明

Java并发编程_volatile关键字的用法