А зачем нужен префикс LOCK, или его аналог InterlockedDecrement при вызове процедуры _LStrClr из модуля System. Данная процедура декрементирует счетчик ссылок строки и при его обнулении освобождает память, ранее занятую строкой.
Суть вопроса была такова — практически невозможно представить себе ситуацию, когда строка потеряет рефы одновременно из двух нитей, а стало быть атомарная операция в данном случае избыточна.
В принципе предпосылка интересная, но…
Но ведь мы передаем строку в класс нити.
Это как минимум приводит к увеличению refCnt, а стало быть мы можем «попасть» на MemLeak в том случае, если бы не использовались атомарные операции при декременте счетчика ссылок.
Это демонстрирует нам код _LStrClr
procedure _LStrClr(var S); {$IFDEF PUREPASCAL} var P: PStrRec; begin if Pointer(S) <> nil then begin P := Pointer(Integer(S) - Sizeof(StrRec)); Pointer(S) := nil; if P.refCnt > 0 then if InterlockedDecrement(P.refCnt) = 0 then FreeMem(P); end; end; {$ELSE} asm { -> EAX pointer to str } MOV EDX,[EAX] { fetch str } TEST EDX,EDX { if nil, nothing to do } JE @@done MOV dword ptr [EAX],0 { clear str } MOV ECX,[EDX-skew].StrRec.refCnt { fetch refCnt } DEC ECX { if < 0: literal str } JL @@done LOCK DEC [EDX-skew].StrRec.refCnt { threadsafe dec refCount } JNE @@done PUSH EAX LEA EAX,[EDX-skew].StrRec.refCnt { if refCnt now zero, deallocate} CALL _FreeMem POP EAX @@done: end; {$ENDIF}
В случае использования неатомарного декремента инструкция JNE имеет огромный шанс выполниться не верно. (И она действительно выполнится не верно, если убрать LOCK префикс).
Я конечно пробовал объяснить данную ситуацию примерами из интеловского мануала, где объясняется работа, но в итоге решил реализовать следующий пример (которым и смог убедить автора вопроса):
program interlocked; {$APPTYPE CONSOLE} uses Windows; const Limit = 1000000; DoubleLimit = Limit shl 1; var SameGlobalVariable: Integer; function Test1(lpParam: Pointer): DWORD; stdcall; var I: Integer; begin for I := 0 to Limit - 1 do asm lea eax, SameGlobalVariable inc [eax] // обычный инкремент end; end; function Test2(lpParam: Pointer): DWORD; stdcall; var I: Integer; begin for I := 0 to Limit - 1 do asm lea eax, SameGlobalVariable lock inc [eax] // атомарный инкремент end; end; var I: Integer; hThread: THandle; ThreadID: DWORD; begin // Неатомарное увеличение значения переменной SameGlobalVariable SameGlobalVariable := 0; hThread := CreateThread(nil, 0, @Test1, nil, 0, ThreadID); for I := 0 to Limit - 1 do asm lea eax, SameGlobalVariable inc [eax] // обычный инкремент end; WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); if SameGlobalVariable <> DoubleLimit then Writeln('Step one failed. Expected: ', DoubleLimit, ' but current: ', SameGlobalVariable); // Атомарное увеличение значения переменной SameGlobalVariable SameGlobalVariable := 0; hThread := CreateThread(nil, 0, @Test2, nil, 0, ThreadID); for I := 0 to Limit - 1 do asm lea eax, SameGlobalVariable lock inc [eax] // атомарный инкремент end; WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); if SameGlobalVariable <> DoubleLimit then Writeln('Step two failed. Expected: ', DoubleLimit, ' but current: ', SameGlobalVariable); Readln; end.
Суть примера — есть некая глобальная переменная SameGlobalVariable (она выступает в роли счетчика ссылок строки из изначальной постановки задачи) и выполняются изменения ее значения в обычном и атомарном режимах с использованием нити.
Здесь наглядно видно различия между двумя режимами работы.
В консоли вы увидите примерно следующее:
Step one failed. Expected: 2000000 but current: 1018924
Ошибки по второму варианту реализации вы не увидите никогда 🙂
Кстати первый вариант может использоваться в качестве достаточно хорошего рандомизатора (о котором я говорил в предыдущих статьях).
Резюмируя:
Анализ исходного кода системных модулей Delphi и VCL в частности, иногда может вам дать гораздо больше информации, чем предположения о том как оно работает на самом деле и это факт, но…
Нет, это не факт, это больше чем факт — так оно и было на самом деле ©
— © Александр (Rouse_) Багель
Апрель, 2013
ссылка на оригинал статьи http://habrahabr.ru/post/181694/
Добавить комментарий