C#编译器优化和volatile关键字
我已经阅读了一些关于volatile
关键字和没有此关键字的行为的post。
我特别测试了C#中说明volatile关键字用法的答案代码。 运行时,我会在发布模式下观察exception行为,不附加调试器。 到目前为止,没有问题。
所以,据我所知,以下代码永远不会退出。
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. } }
为什么退出? 即使在发布模式下,没有调试器?
更新
修改了C#中对volatile关键字的使用的代码。
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) { } }
在没有调试器的情况下,在发布模式下,该程序不会退出。
所以,据我所知,以下内容永远不会退出。
不,它可以停止。 它不能保证 。
例如,它不会停留在我正在运行的机器上 – 但同样我可以在另一台机器上尝试完全相同的可执行文件,它可能表现良好。 它将取决于它运行的CLR使用的确切内存模型语义。 这将受到底层架构的影响,甚至可能是使用的确切CPU。
重要的是要注意,不是C#编译器决定如何处理volatile
字段 – C#编译器只是使用System.Runtime.CompilerServices.IsVolatile
指示元数据的易变性。 然后, JIT可以在遵守相关合同方面找出其意义。
在评论中,您说您的目标是32位x86架构。 这个很重要。 此外,我的答案是假设已经意识到仅仅因为内存模型允许某些事情发生并不意味着它总会发生。
简短回答:
这是因为while
循环是空的。 当然,许多其他微妙的变化也会影响行为。 例如,如果在循环之前调用Console.WriteLine
或Thread.MemoryBarrier
,则行为将发生变化。
答案很长:
32位和64位运行时的行为方式有所不同。 无论出于何种原因,32位运行时都是while
循环之前没有显式/隐式内存生成器或者while
循环本身为空时提升优化。
从这里关于同一主题的另一个问题考虑我的例子。 这是再次下面。
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进行了测试。 我相信你也应该注意到环境的变化。 尝试上面的修改。