Atomic operations
Just the other day they asked me a question.
And why do you need the LOCK prefix, or its analogue InterlockedDecrement when calling the _LStrClr procedure from the System module. This procedure decrements the link reference counter of the line and when it is reset, it frees the memory previously occupied by the line.
The essence of the question was this: it is almost impossible to imagine a situation where a string will lose refs simultaneously from two threads, and therefore the atomic operation in this case is redundant.
In principle, the premise is interesting, but ...
But we pass the string to the thread class.
This at least leads to an increase in refCnt, and therefore we can “get” to MemLeak if atomic operations were not used when decrementing the reference counter.
This demonstrates the _LStrClr code:
In the case of using a non-atomic decrement, the JNE instruction has a huge chance of being executed incorrectly. (And it really will not be executed correctly if you remove the LOCK prefix).
Of course, I tried to explain this situation with examples from the Intel manual, where the work is explained, but in the end I decided to implement the following example (with which I was able to convince the author of the question):
The essence of the example is a certain global variable SameGlobalVariable (it acts as a line reference counter from the original statement of the task) and changes are made to its value in normal and atomic modes using a thread.
Here you can clearly see the differences between the two modes of operation.
In the console, you will see something like the following:
Errors in the second embodiment you will never see.
By the way, the first option can be used as a good enough randomizer (which I talked about in previous articles).
Summarizing:
An analysis of the source code of Delphi and VCL system modules in particular can sometimes give you much more information than assumptions about how it actually works and this is a fact, but ...
No, this is not a fact, it is more than a fact - this is how it really was
And why do you need the LOCK prefix, or its analogue InterlockedDecrement when calling the _LStrClr procedure from the System module. This procedure decrements the link reference counter of the line and when it is reset, it frees the memory previously occupied by the line.
The essence of the question was this: it is almost impossible to imagine a situation where a string will lose refs simultaneously from two threads, and therefore the atomic operation in this case is redundant.
In principle, the premise is interesting, but ...
But we pass the string to the thread class.
This at least leads to an increase in refCnt, and therefore we can “get” to MemLeak if atomic operations were not used when decrementing the reference counter.
This demonstrates the _LStrClr code:
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}
In the case of using a non-atomic decrement, the JNE instruction has a huge chance of being executed incorrectly. (And it really will not be executed correctly if you remove the LOCK prefix).
Of course, I tried to explain this situation with examples from the Intel manual, where the work is explained, but in the end I decided to implement the following example (with which I was able to convince the author of the question):
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.
The essence of the example is a certain global variable SameGlobalVariable (it acts as a line reference counter from the original statement of the task) and changes are made to its value in normal and atomic modes using a thread.
Here you can clearly see the differences between the two modes of operation.
In the console, you will see something like the following:
Step one failed. Expected: 2000000 but current: 1018924
Errors in the second embodiment you will never see.
By the way, the first option can be used as a good enough randomizer (which I talked about in previous articles).
Summarizing:
An analysis of the source code of Delphi and VCL system modules in particular can sometimes give you much more information than assumptions about how it actually works and this is a fact, but ...
No, this is not a fact, it is more than a fact - this is how it really was