I was assuming that if a shared variable between threads has native type, atomicity should do the job.
But as per output of the code below, it is not the case, at least for delphi.
Thread t1 is simply incrementing the counter 10M times. At the same time, thread t2 is decrementing the counter 10M times. So expected counter value at the end is 0 but I read different values each time.
What is the proper way of sharing a native variable between threads in Delphi without locking?
procedure TForm1.Button1Click(Sender: TObject);
var
t1, t2: TThread;
Counter: NativeInt;
begin
Counter := 0;
// first thread to increment shared counter
t1 := TThread.CreateAnonymousThread(
procedure ()
var
i: Integer;
begin
for i := 1 to 10000000 do
Inc(Counter);
end
);
// second thread to decrement shared counter
t2 := TThread.CreateAnonymousThread(
procedure ()
var
i: Integer;
begin
for i := 1 to 10000000 do
Dec(Counter);
end
);
t1.FreeOnTerminate := false;
t2.FreeOnTerminate := false;
// start threads
t1.Start;
t2.Start;
// wait for them to finish
t1.WaitFor;
t2.WaitFor;
t1.Free;
t2.Free;
// print the counter, expected counter is 0
Caption := IntToStr(Counter);
end;
Reading and writing of aligned variables is atomic. But the problem is that when you use inc
and dec
you are both reading and writing. By performing two memory accesses then the compound operation is no longer atomic.
Use atomic increment functions instead. The TInterlocked
class methods, or AtomicIncrement
.
As far as what is native about NativeInt
, that refers to its size. It is an integral type the same size as a pointer. So 32 bits in a 32 bit process, 64 bits in a 64 bit process. These types are seldom used for pure Delphi code, usually for interop with third party libraries which might declare handle types using pointer sized integers.