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.