1

编辑:我终于写了一篇关于这个问题的完整文章:同步、内存可见性和泄漏抽象


我用这段代码展示了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提供了生成的汇编代码并分享了一些调试智慧。

4

1 回答 1

3

正如您所写,这一切都在 JITter 中。在 Release 构建中并且没有附加调试器,++n您将获得:

            int n = 0;
00000000  push        ebp 
00000001  mov         ebp,esp 
            while (!ok) ++n;
00000003  movzx       eax,byte ptr [ecx+4] 
00000007  test        eax,eax 
00000009  jne         0000000F 
0000000b  test        eax,eax      ; <---
0000000d  je          0000000B     ; <---
0000000f  pop         ebp 
        }
00000010  ret 

并且没有++n

            while (!ok) ;
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  cmp         byte ptr [ecx+4],0 
00000007  je          00000003 
00000009  pop         ebp 
        }
0000000a  ret 

真正的问题应该是为什么根本没有++n发出的代码。

编辑:在 x64 Release 构建结果类似:

            Debugger.Break();
00000000  push        rbx 
00000001  sub         rsp,20h 
00000005  mov         rbx,rcx 
00000008  call        FFFFFFFFED0EE4D0 
0000000d  mov         ecx,2710h 
00000012  call        FFFFFFFFEDCFE460 
            while (!ok) ++n;
00000017  mov         al,byte ptr [rbx+8] 
0000001a  movzx       ecx,al 
0000001d  test        ecx,ecx 
0000001f  jne         0000000000000025 
00000021  test        ecx,ecx 
00000023  je          0000000000000021 
00000025  add         rsp,20h 
00000029  pop         rbx 
0000002a  rep ret 
于 2013-11-23T21:41:19.557 回答