C#编译器优化和volatile关键字

时间:2013-07-28 11:48:53

标签: c# .net multithreading volatile

我已经阅读了一些关于volatile关键字和没有此关键字的行为的帖子。

我特别测试了Illustrating usage of the volatile keyword in C#答案中的代码。运行时,我会在发布模式下观察异常行为,不附加调试器。到目前为止,没有问题。

因此,据我所知,以下代码永远不会退出。

public class Program
{
    private bool stopThread;

    public void Test()
    {
        while (!stopThread) { }  // Read stopThread which is not marked as volatile
        Console.WriteLine("Stopped.");
    }


    private static void Main()
    {
        Program program = new Program();

        Thread thread = new Thread(program.Test);
        thread.Start();

        Console.WriteLine("Press a key to stop the thread.");
        Console.ReadKey();

        Console.WriteLine("Waiting for thread.");
        program.stopThread = true;

        thread.Join();  // Waits for the thread to stop.
    }
}

为什么退出?即使在发布模式下,也没有调试器?

更新

改编Illustrating usage of the volatile keyword in C#的代码。

private bool exit;

public void Test()
{
    Thread.Sleep(500);
    exit = true;
    Console.WriteLine("Exit requested.");
}

private static void Main()
{
    Program program = new Program();

    // Starts the thread
    Thread thread = new Thread(program.Test);
    thread.Start();

    Console.WriteLine("Waiting for thread.");
    while (!program.exit) { }
}

在Release模式下,如果没有附带调试器,该程序不会退出。

2 个答案:

答案 0 :(得分:10)

  

所以,据我所知,以下内容永远不会退出。

不,可以停止。它只是保证到。

它不会停留在我正在运行的机器上,例如 - 但同样我可以在另一台机器上尝试完全相同的可执行文件,它可能表现良好。它将取决于它运行的CLR使用的确切内存模型语义。这将受到底层架构的影响,甚至可能是正在使用的CPU。

重要的是要注意, C#编译器决定如何处理volatile字段 - C#编译器只使用System.Runtime.CompilerServices.IsVolatile指示元数据的易变性。然后 JIT 可以在遵守相关合同方面找出其意义。

答案 1 :(得分:1)

在评论中,您说您的目标是32位x86架构。这个很重要。另外,我的答案是假设已经知道仅仅因为内存模型允许发生某事并不意味着总是会发生。

简答:

这是因为while循环为空。当然,许多其他微妙的变化也会影响行为。例如,如果您在循环之前调用Console.WriteLineThread.MemoryBarrier,则行为将发生变化。

答案很长:

32位和64位运行时的行为方式有所不同。无论出于何种原因,32位运行时都会在循环之前缺少显式/隐式内存生成器或者while循环本身为空时提升优化。

从另一个关于同一主题here的问题中考虑我的例子。这是下面的。

class Program
{
    static bool stop = false;

    public static void Main(string[] args)
    {
        var t = new Thread(() =>
        {
            Console.WriteLine("thread begin");
            bool toggle = false;
            while (!stop)
            {
                toggle = !toggle;
            }
            Console.WriteLine("thread end");
        });
        t.Start();
        Thread.Sleep(1000);
        stop = true;
        Console.WriteLine("stop = true");
        Console.WriteLine("waiting...");

        // The Join call should return almost immediately.
        // With volatile it DOES.
        // Without volatile it does NOT.
        t.Join(); 
    }
}

此示例确实重现了32位x86硬件上的“无退出”行为。请注意我是如何故意让while循环忙于做某事。无论出于何种原因,空循环都不会一致地重现行为。现在,让我们使用从上面学到的内容改变你的第一个例子,看看会发生什么。

public class Program
{
    private bool stopThread;

    public void Test()
    {
        bool toggle = true;
        while (!stopThread) // Read stopThread which is not marked as volatile
        { 
          toggle = !toggle;
        }  
        Console.WriteLine("Stopped.");
    }


    private static void Main()
    {
        Program program = new Program();

        Thread thread = new Thread(program.Test);
        thread.Start();

        Console.WriteLine("Press a key to stop the thread.");
        Console.ReadKey();

        Console.WriteLine("Waiting for thread.");
        program.stopThread = true;

        thread.Join();  // Waits for the thread to stop.
    }
}

使用第一个示例的略微修改版本来让while循环执行某些操作,您将看到它现在开始显示“无退出”行为。我刚刚在Windows 7 64位上使用针对32位x86的.NET 4.5进行了测试。我相信你也应该注意到环境的变化。尝试上面的修改。