给出以下简单代码:
class Program
{
static bool finish = false;
static void Main(string[] args)
{
new Thread(ThreadProc).Start();
int x = 0;
while (!finish)
{
x++;
}
}
static void ThreadProc()
{
Thread.Sleep(1000);
finish = true;
}
}
并在MSVS2015(.NET 4.6)的发布模式下运行它,我们将得到一个永无止境的应用程序。发生这种情况是因为JIT编译器只生成一次读取finish
的代码,因此忽略了任何未来的更新。
问题是:为什么允许JIT编译器进行这样的优化?规范的哪一部分允许它?
答案 0 :(得分:8)
这将在第10.5.3节 - C# Specification:
中的易失性字段中介绍(我已经强调了下面涵盖你观察的部分)
10.5.3挥发性字段
当字段声明包含volatile修饰符时,该声明引入的字段是易失性字段 对于非易失性字段,重新排序指令的优化技术可能会导致访问没有同步的字段的多线程程序出现意外和不可预测的结果,例如lock-statement(第8.12节)提供的结果。这些优化可以由编译器,运行时系统或硬件执行。对于易失性字段,此类重新排序优化受到限制:
*读取volatile字段称为volatile读取。易失性读取具有“获取语义”;也就是说,它保证在指令序列之后发生的任何内存引用之前发生 *写入易失性字段称为易失性写入。易失性写入具有“释放语义”;也就是说,保证在指令序列中的写指令之前的任何存储器引用之后发生 这些限制确保所有线程将按照执行顺序观察由任何其他线程执行的易失性写入。从所有执行线程中可以看出,不需要符合要求的实现来提供易失写入的单个总排序。
答案 1 :(得分:2)
编译器认为(做出以下承诺)如下:
我将按照您的顺序执行您的代码 问我,那是在你的单线程中运行的任何指令 代码将按照它们编写的顺序执行并基于 这个我会做任何我喜欢的优化,符合 单线程顺序性。
好吧,编译器看到finish
变量未标记为volatile
,因此他认为它不会被其他线程更改,因此他将其优化为将条件视为始终为真。
在调试模式下,它有一个更宽松的思路,并且不会执行此优化。
有关此here的更多信息。