首页 文章

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

提问于
浏览
87

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

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

那是我无法实现的 . 即使尝试多次,启用优化等,我总是会在没有'volatile'关键字的情况下获得正确的行为 .

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

6 回答

  • 21

    我已经实现了一个有效的例子!

    从wiki收到的主要想法,但对C#进行了一些更改 . 维基文章为C的静态字段演示了这一点,它看起来像C#总是小心地将请求编译到静态字段......我用非静态字段做例子:

    如果在 Release 模式和 without debugger 中运行此示例(即使用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");
        }
    }
    
  • 4

    是的,它's hardware dependent (you are unlikely to see the problem without multiple processors), but it'也是依赖于实现的 . CLR规范中的内存模型规范允许Microsoft实现CLR不一定要做的事情 . 我在volatile关键字上看到的最好的文档是this blog post by Joe Duffy . 请注意,他说MSDN文档是"highly misleading."

  • 2

    当没有指定'volatile'关键字时,发生错误并不是真正的问题,更多的是当未指定错误时可能发生错误 . 一般来说,你会知道什么时候比编译器更好!

    最简单的思考方式是编译器可以,如果需要,可以内联某些值 . 通过将值标记为volatile,您告诉自己和编译器该值可能实际发生更改(即使编译器不这么认为) . 这意味着编译器不应该内联值,保持缓存或提前读取值(尝试优化) .

    此行为与C中的关键字不完全相同 .

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

  • 101

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

    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使用一个;但你可以手动禁用它) .

  • 4

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

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

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

    干杯,谢谢你们所有的鱼 . 基思 .


    PS: 我希望 static volatile int在 static int行为不正常的情况下正常运行 .

    我已经尝试过这次挑战并且失败了 . (其实我很快放弃了;-) . 在我尝试使用静态变量的所有内容中,无论它们是否是易失性的,它们都表现为"correctly"而且我在寄存器中缓存静态变量的值(即它缓存了对该堆地址的引用)?

    不,这不是企图将社区引回原来的问题 .

  • 6

    我看到Joe Albahari的以下文字给了我很多帮助 .

    我从上面的文本中 grab 了一个例子,我通过创建静态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
        }
    }
    

相关问题