在flag字段上使用volatile关键字

时间:2017-11-15 13:44:01

标签: c# multithreading volatile

我尝试了解多线程上下文中volatile的使用。在以下来自Internet上another source of knowledge的代码中:

class Program
{
    static string _result;
    //static volatile bool _done;
    static bool _done;

    static void SetVolatile()
    {
        // Set the string.
        _result = "Dot Net Perls";
        // The volatile field must be set at the end of this method.
        _done = true;
    }

    static void Main()
    {
        // Run the above method on a new thread.
        new Thread(new ThreadStart(SetVolatile)).Start();

        // Wait a while.
        Thread.Sleep(200);

        // Read the volatile field.
        if (_done)
        {
            Console.WriteLine(_result);
        }
    }
}

演示使用 volatile 关键字可以防止线程读取存储在缓存中的值。而不是这个,它应该检查实际值。

因此,如果没有volatile _done仍然应该有一个false值(从缓存中读取),并且不应该执行Console.WriteLine语句。

不幸的是,在没有volatile关键字的调试/发布模式下运行此代码总会产生输出。这个具体例子有什么意义?

2 个答案:

答案 0 :(得分:3)

如前所述,不使用volatile关键字并不意味着在所有情况下都必须缓存所有读取。它们可能是缓存的,也可能不是。但是如果你想要更多可重复的例子,试试这个:

class Program {
    static string _result;
    //static volatile bool _done;
    static bool _done;
    static void SetVolatile() {
        // Set the string.
        _result = "Dot Net Perls";
        // The volatile field must be set at the end of this method.
        _done = true;
    }

    static void Main() {
        // Run the above method on a new thread.
        new Thread(new ThreadStart(SetVolatile)).Start();

        // prevent compiler to throw away empty while loop
        // by doing something in it
        int i = 0;
        while (!_done) {
            i++;
        }
        Console.WriteLine("done " + i);
    }
}

在这里,您在while循环中反复阅读_done,增加了缓存的可能性。程序应以" done"终止。消息但不会,因为从另一个线程更改为_done将不会被注意到。

答案 1 :(得分:0)

最好阅读ECMA-335,§I.12.6
要点是:

  • 程序可以优化(很多)
  • 将CIL转换为本机代码的优化编译器不应删除任何易失性操作也不应将多个易失性操作合并为单个操作


因此,在这种情况下,您的代码可以进行优化。
请尝试以下代码:

private bool flag = true;
public void LoopReadHoistingTest()
{
    Task.Run(() => { flag = false; });
    while (flag)
    {
      // Do nothing
    }
}

调试模式下(没有优化)它可以正常工作。在发布模式(带优化)时,它将永久挂起,因为在循环外移动读取是非常常见的优化。
但是,如果您标记字段volatile(或使用Volatile.Read方法或某些Interlocked方法),它将起作用,因为在这种情况下禁止优化。

在你的例子中(没有循环),Thread.Sleep产生一个隐式的内存屏障(因为它不被禁止,它使代码工作的意外更少),所以它将从内存中读取值。但我没有看到任何规范说它必须做一个隐式内存屏障,所以在某些实现中它可能不是真的(或者我们必须在规范中找到它。)