1

編集:私はついにこの問題に関する完全な記事を書きました:同期、メモリの可視性、漏れやすい抽象化


このコードを使用して、揮発性読み取りの重要性を示しています。

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と、スレッドは新しい値を読み取って終了します。

CILに関する限り(少なくとも私のような素人にとっては)何もないため、 JITterの最適化と関係があると思います。

.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の黒魔術です。

これが「よく知られた」JITter「問題」であることを確認し、x64では「期待される」動作が得られることを指摘してくれた Hans に感謝します。

結果のアセンブリ コードを提供し、デバッグの知恵を共有してくれたMagnatLUに感謝します。

4

1 に答える 1

3

あなたが書いたように、それはすべてJITterにあります。リリース ビルドで、デバッガーが接続されてい++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 リリースのビルド結果は似ています:

            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 に答える