编辑:我终于写了一篇关于这个问题的完整文章:同步、内存可见性和泄漏抽象
我用这段代码展示了volatile 读取的重要性:
bool ok = false;
void F()
{
int n = 0;
while (!ok) ++n;
}
public void Run()
{
Thread thread = new Thread(F);
thread.Start();
Console.Write("Press enter to notify thread...");
Console.ReadLine();
ok = true;
Console.WriteLine("Thread notified.");
}
正如预期的那样,线程不知道新ok
值并且程序挂起。
但是为了获得这种行为,我必须在while
循环中做一些事情,例如增加一个整数。
如果我删除该++n
语句,线程将读取新值并退出。
我想这与JITter 优化有关,因为就 CIL 而言,什么都没有(至少对于像我这样的外行来说):
.method private hidebysig instance void F() cil managed
{
.maxstack 2
.locals init ([0] int32 n)
IL_0000: ldc.i4.0
IL_0001: stloc.0
IL_0002: br.s IL_0008
IL_0004: ldloc.0
IL_0005: ldc.i4.1
IL_0006: add
IL_0007: stloc.0
IL_0008: ldarg.0
IL_0009: ldfld bool ThreadingSamples.MemoryVisibilitySample::ok
IL_000e: brfalse.s IL_0004
IL_0010: ret
}
.method private hidebysig instance void F() cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld bool ThreadingSamples.MemoryVisibilitySample::ok
IL_0006: brfalse.s IL_0000
IL_0008: ret
}
而且,相反,我天真地期望在循环中执行某些操作会增加线程触发缓存刷新的几率。
我又错过了什么?
最终编辑:这又是一些JITter黑魔法。
感谢 Hans 确认这是一个“众所周知的”JITter“问题”,并指出在x64中我们得到了“预期的”行为。
感谢 MagnatLU提供了生成的汇编代码并分享了一些调试智慧。