说明在C#中使用volatile关键字
我想编写一个可视化地演示volatile
关键字行为的小程序。 理想情况下,它应该是一个执行并发访问非易失性静态字段,并因此得到不正确行为的程序。
在同一个程序中添加volatile关键字可以解决这个问题。
这是我无法实现的。 即使尝试多次,启用优化等,我总是得到一个正确的行为,没有“挥发性”的关键字。
你对这个话题有什么想法吗? 你知道如何在简单的演示程序中模拟这样的问题吗? 它依赖于硬件吗?
我已经实现了一个工作的例子!
主要想法来自维基,但有一些C#的变化。 这篇wiki文章演示了C ++的静态字段,看起来像C#总是仔细地将请求编译到静态字段中……而我用非静态字段做了例子:
如果你在Release模式下运行这个例子而没有debugging器 (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"); } }
是的,它取决于硬件(如果没有多个处理器,你不会看到这个问题),但是这也取决于实现。 CLR规范中的内存模型规范允许Microsoft实现CLR的东西不一定。 我在volatile关键字上看到的最好的文档是Joe Duffy的博客文章 。 请注意,他说MSDN文档“非常具有误导性”。
当没有指定'volatile'关键字时,这不是真正的故障发生的问题,更多的是指定没有指定时可能发生的错误。 一般来说,你会知道什么时候比编译器更好的情况!
想想最简单的方法就是编译器可以,如果想要的话,可以内联某些值。 通过将值标记为volatile,您告诉自己和编译器该值可能实际上发生了变化(即使编译器不这么认为)。 这意味着编译器不应该内联值,保持caching或者提前读取值(试图优化)。
这种行为与C ++中的关键字并不相同。
MSDN 在这里有一个简短的描述。 这可能是一个更深入的波动,primefaces性和互锁主题的post
在C#中很难演示,因为代码被虚拟机抽象出来,因此在本机的一个实现中它的工作原理没有变化,而在另一个实例上可能会失败。
不过,维基百科有一个很好的例子,如何用C语言来演示它。
如果JIT编译器确定variables的值无论如何都不能改变,并且因此创build的机器代码甚至不再检查,那么在C#中也会发生同样的情况。 如果现在另一个线程正在改变这个值,你的第一个线程仍然可能被捕获到循环中。
另一个例子是忙碌的等待。
同样,这也可能发生在C#上,但它强烈地依赖于虚拟机和JIT编译器(或解释器,如果它没有JIT …理论上,我认为MS总是使用JIT编译器,而且Mono使用一个,但你可能可以手动禁用它)。
这是我对这种行为的集体理解的贡献……这不是很多,只是一个演示(基于xkip的演示),它演示了一个volatile和非volatile(即“normal”)int值的行为,side-by在同一个程序中,当我find这个线程时,这就是我正在寻找的东西。
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
(至less知道不要依赖于volatile
地方,我的第一本能是lock
它)。
欢呼声,并感谢所有的鱼。 基思。
PS:我对原始请求的演示非常感兴趣,它是:“我希望看到一个静态易失性int在静态 int行为不当的地方正确运行。
我已经尝试过,并且没有成功。 (其实我很快就放弃了;-)。 在所有我使用静态variables进行尝试时,无论它们是否是不稳定的,它们的行为都是“正确的”,而且我很乐意解释为什么事实上是这样,如果事实确实如此,编译器不会caching寄存器中的静态variables的值 (即它caching对该堆地址的引用 )?
不,这不是一个新问题……这是企图让社区回到原来的问题。
我遇到了Joe Albahari的以下文字,帮助了我很多。
- 内存障碍和波动
我从上面的文本中抓取了一个例子,我通过创build一个静态的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 } }