2013-11-23 2 views
1

EDIT: Я, наконец, написал полную статью о выпуске: Synchronization, memory visibility and leaky abstractions.Net видимость памяти поведение


Я демонстрирует важность летучего чтения с этим кодом:

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 обеспокоен нет ничего (по крайней мере, для неспециалиста, как я):

.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 
} 

И, наоборот, Я бы наивно ожидал, что выполнение чего-то в цикле увеличит вероятность того, что поток инициирует обновление кеша.

Что мне еще не хватает?


FINAL EDIT: это снова некоторые джиттер черная магия.

Kudos to Hans для подтверждения этого является «хорошо известной» проблемой JITter и указывает, что в x64 мы получаем «ожидаемое» поведение.

Kudos to MagnatLU для предоставления полученного кода сборки и обмена некоторой мудростью отладки.

+0

Это хорошо известное поведение x86 джиттера. Не произойдет, когда вы используете джиттер x64. Не имеет большого отношения к джиттеру, bool не является объектом синхронизации. –

+0

В чем вопрос? В вашем вопросе упоминается «изменчивое чтение», но вы на самом деле этого не делаете, так что на самом деле вопрос? –

+0

@ HansPassant: Спасибо за быстрый ответ. Действительно, в x64 поведение «правильно» без '++ n'. Больше информации о том, почему и как? Что-то общего с моделями памяти x86 и x64 или чистым CLR?PS: Пожалуйста, Ганс не предполагает, что то, что вы знаете, «хорошо известно», ваши ответы показывают, что вы знаете гораздо больше, чем средний неспециалист. ;) – Pragmateek

ответ

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 излучается на всех.

Edit: на 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 
+0

Хмм интересно, поэтому с '++ n' он использует регистр, тогда как без него читается« прямо »из памяти. Спасибо, что нашли время, чтобы извлечь собственный код; с некоторым обманом WinDBG, я думаю? :) Благодаря. – Pragmateek

+1

@Pragmateek на самом деле его довольно легко сделать прямо в визуальной студии, просто приостановите свою программу во время ее работы и перейдите в 'Debug-> Windows-> Dissasembly', и он покажет вам собранный код, созданный JITer. –

+2

Все сделано в VS. Вы должны обмануть .NET, чтобы использовать правильный JITter. Даже запуск сборки Release из VS по-прежнему использует не оптимизирующий компилятор. Вы должны запускать сборку Release напрямую и либо присоединить VS к запущенному процессу ** после того, как ** ваш метод был помечен, либо разместите 'Debugger.Break();' в интересующем вас методе и разрешите ОС подключать VS к нему для вас. – MagnatLU

Смежные вопросы