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

时间:2008-09-25 13:20:04

标签: c# .net volatile

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

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

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

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

5 个答案:

答案 0 :(得分:101)

我已经取得了一个成功的例子!

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

如果您在发布模式下运行此示例而不使用调试器(即使用Ctrl + F5),则行while (test.foo != 255)将优化为'while(是的)'这个程序永远不会回来。 但是在添加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");
    }
}

答案 1 :(得分:21)

是的,它依赖于硬件(如果没有多个处理器,你不太可能看到问题),但它也依赖于实现。 CLR规范中的内存模型规范允许Microsoft实现CLR不一定要做的事情。我在volatile关键字上看到的最好的文档是this blog post by Joe Duffy。请注意,他说MSDN文档“极具误导性。”

答案 2 :(得分:6)

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

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

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

MSDN有简短描述here。 这是一篇关于Volatility, Atomicity and Interlocking

主题的更深入的帖子

答案 3 :(得分:4)

在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 :(得分:2)

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

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