Threads & Parallel Programming
UI controls (LCL, VCL, FMX) are not thread-safe: only touch them from the main thread via Synchronize or Queue.
35.1 TThread basics
program ThreadDemo;
{$mode objfpc}{$H+}
uses
{$IFDEF UNIX}cthreads,{$ENDIF} // FPC on Unix: must be the first unit for thread support
Classes, SysUtils, SyncObjs;
type
TWorker = class(TThread)
private
FId: Integer;
protected
procedure Execute; override;
public
constructor Create(AId: Integer);
end;
var
Lock: TCriticalSection;
Total: Int64 = 0;
constructor TWorker.Create(AId: Integer);
begin
inherited Create(True); // create suspended
FId := AId;
FreeOnTerminate := False; // we will WaitFor and Free ourselves
end;
procedure TWorker.Execute;
var
I: Integer;
Local: Int64;
begin
Local := 0;
for I := 1 to 1000000 do
begin
if Terminated then Exit; // cooperative cancellation
Inc(Local, I mod 7);
end;
Lock.Enter; // protect shared data
try
Inc(Total, Local);
finally
Lock.Leave;
end;
end;
var
Workers: array[1..4] of TWorker;
I: Integer;
begin
Lock := TCriticalSection.Create;
try
for I := 1 to 4 do
begin
Workers[I] := TWorker.Create(I);
Workers[I].Start;
end;
for I := 1 to 4 do
begin
Workers[I].WaitFor;
Workers[I].Free;
…35.2 TThread members
override with the thread's work (runs in the new thread) create; call Start if created suspended Terminate / Terminated request / check cooperative cancellation block until the thread finishes; returns ReturnValue thread frees itself when done (then never touch it after Start) run code in the main thread and wait (UI updates) run code in the main thread asynchronously event fired in the main thread after Execute thread priority (Windows mostly) quick thread from an anonymous method (Delphi)
TThread.CurrentThread, TThread.ProcessorCount UI controls (LCL, VCL, FMX) are not thread-safe: only touch them from the main thread via Synchronize or Queue. In console programs, the main thread must call CheckSynchronize periodically for Synchronize/Queue to work.
35.3 Synchronization primitives
mutual exclusion within a process TRTLCriticalSection + InitCriticalSection/EnterCriticalSection/LeaveCriticalSection/DoneCriticalSection low-level critical section TMonitor.Enter(Obj) / Exit System (Delphi) lock on any object many readers, one writer TEvent, TSimpleEvent signal between threads TSemaphore, TMutex counting / cross-process locks
InterlockedIncrement, InterlockedExchangeAdd System (FPC), Windows (Delphi)
TInterlocked.Increment, AtomicIncrement SyncObjs / intrinsic (Delphi) per-thread global variables
TThreadList, TThreadedQueue<T> (Delphi) Classes / System.Generics.Collections
35.4 Delphi Parallel Programming Library (PPL)
TTask.WaitForAll, TTask.WaitForAny, and TThreadPool give more control.
// Delphi XE7+
uses System.Threading, System.SyncObjs, System.SysUtils;
procedure ParallelDemo;
var
Sum: Int64;
Task: ITask;
Answer: IFuture<Integer>;
begin
Sum := 0;
TParallel.For(1, 1000, procedure(I: Integer)
begin
TInterlocked.Add(Sum, I); // atomic add
end);
Writeln('Sum = ', Sum); // 500500
Task := TTask.Run(procedure
begin
Sleep(200); // background work
TThread.Queue(nil, procedure
begin
Writeln('Task finished'); // runs on the main thread
end);
end);
Answer := TTask.Future<Integer>(function: Integer
begin
Result := 6 * 7;
end);
Writeln('Future = ', Answer.Value); // blocks until computed
Task.Wait;
end;35.5 Parallel loops in Lazarus
The MultiThreadProcs package (unit MTProcs) provides ProcThreadPool.DoParallel(@MyProc, StartIndex, EndIndex, Data) for parallel loops; FPC 3.2 has no PPL equivalent in the RTL.
35.6 Threading pitfalls
Data races: protect every shared mutable variable, or avoid sharing (give each thread its own data and combine results at the end, as in 35.1).
Deadlocks: always acquire multiple locks in the same order; keep locked regions short.
Never call Synchronize while holding a lock the main thread may need.
FreeOnTerminate := True + accessing the thread object later = use-after-free.
Strings and dynamic arrays have thread-safe reference counts, but concurrent modification of the same variable is still a race.