Atomic Counters in Pascal
Our primary mechanism for managing state in Pascal is through shared variables. However, when dealing with multiple threads, we need to ensure that these shared variables are accessed safely. In this example, we’ll look at using atomic operations for thread-safe counters accessed by multiple threads.
program AtomicCounters;
uses
SysUtils, Classes, SyncObjs;
var
Ops: Int64;
OpLock: TCriticalSection;
WaitGroup: TCountdownEvent;
procedure IncrementCounter;
var
i: Integer;
begin
for i := 1 to 1000 do
begin
// To atomically increment the counter we use a critical section
OpLock.Acquire;
try
Inc(Ops);
finally
OpLock.Release;
end;
end;
WaitGroup.Signal;
end;
var
i: Integer;
Threads: array[1..50] of TThread;
begin
Ops := 0;
OpLock := TCriticalSection.Create;
WaitGroup := TCountdownEvent.Create(50);
try
// We'll start 50 threads that each increment the counter exactly 1000 times.
for i := 1 to 50 do
begin
Threads[i] := TThread.CreateAnonymousThread(IncrementCounter);
Threads[i].Start;
end;
// Wait until all the threads are done.
WaitGroup.WaitFor;
// Here no threads are writing to 'Ops', but we still use the lock
// to ensure safe reading of the value.
OpLock.Acquire;
try
WriteLn('ops: ', Ops);
finally
OpLock.Release;
end;
finally
OpLock.Free;
WaitGroup.Free;
for i := 1 to 50 do
Threads[i].Free;
end;
end.
We expect to get exactly 50,000 operations. If we had used a non-atomic integer and incremented it without synchronization, we’d likely get a different number, changing between runs, because the threads would interfere with each other.
To run this program:
$ fpc atomic_counters.pas
$ ./atomic_counters
ops: 50000
In this Pascal implementation, we use a TCriticalSection
to ensure atomic access to our counter. This is similar to a mutex in other languages. We also use a TCountdownEvent
as an equivalent to Go’s WaitGroup
to wait for all threads to complete.
The TThread.CreateAnonymousThread
method is used to create threads, which is similar to launching goroutines in Go. Each thread runs the IncrementCounter
procedure 1000 times.
Note that Pascal doesn’t have built-in atomic operations like Go does, so we have to use explicit locking mechanisms. This makes the code a bit more verbose, but it achieves the same thread-safe counter functionality.
Next, we’ll look at mutexes, another tool for managing state in concurrent programs.