Waitgroups in Pascal

Our first program will demonstrate the use of WaitGroups to synchronize multiple threads. Here’s the full source code:

program WaitGroups;

{$mode objfpc}{$H+}

uses
  SysUtils, Classes;

type
  TWorker = class(TThread)
  private
    FId: Integer;
  public
    constructor Create(AId: Integer);
    procedure Execute; override;
  end;

  TWaitGroup = class
  private
    FCounter: Integer;
    FLock: TCriticalSection;
    FEvent: TEvent;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Add(Delta: Integer);
    procedure Done;
    procedure Wait;
  end;

var
  WaitGroup: TWaitGroup;

constructor TWorker.Create(AId: Integer);
begin
  inherited Create(False);
  FreeOnTerminate := True;
  FId := AId;
end;

procedure TWorker.Execute;
begin
  WriteLn(Format('Worker %d starting', [FId]));
  Sleep(1000); // Simulate an expensive task
  WriteLn(Format('Worker %d done', [FId]));
  WaitGroup.Done;
end;

constructor TWaitGroup.Create;
begin
  inherited;
  FCounter := 0;
  FLock := TCriticalSection.Create;
  FEvent := TEvent.Create(nil, True, False, '');
end;

destructor TWaitGroup.Destroy;
begin
  FLock.Free;
  FEvent.Free;
  inherited;
end;

procedure TWaitGroup.Add(Delta: Integer);
begin
  FLock.Enter;
  try
    Inc(FCounter, Delta);
    if FCounter > 0 then
      FEvent.ResetEvent;
  finally
    FLock.Leave;
  end;
end;

procedure TWaitGroup.Done;
begin
  FLock.Enter;
  try
    Dec(FCounter);
    if FCounter = 0 then
      FEvent.SetEvent;
  finally
    FLock.Leave;
  end;
end;

procedure TWaitGroup.Wait;
begin
  FEvent.WaitFor(INFINITE);
end;

var
  I: Integer;

begin
  WaitGroup := TWaitGroup.Create;
  try
    for I := 1 to 5 do
    begin
      WaitGroup.Add(1);
      TWorker.Create(I);
    end;

    WaitGroup.Wait;
  finally
    WaitGroup.Free;
  end;
end.

This program demonstrates the use of a WaitGroup to synchronize multiple threads in Pascal. Here’s a breakdown of the code:

  1. We define a TWorker class that extends TThread. Each worker prints a message when starting and finishing, and simulates work with a Sleep call.

  2. We implement a TWaitGroup class that mimics the functionality of Go’s WaitGroup. It uses a counter, a critical section for thread-safe operations, and an event for synchronization.

  3. In the main program, we create a TWaitGroup instance and launch 5 worker threads.

  4. Each worker thread is created with WaitGroup.Add(1) to increment the counter.

  5. The WaitGroup.Wait method is called to block until all workers have finished.

  6. Each worker calls WaitGroup.Done when it completes its work.

To run this program, save it as waitgroups.pas and compile it with the Free Pascal Compiler:

$ fpc waitgroups.pas
$ ./waitgroups

The output will be similar to:

Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 4 starting
Worker 5 starting
Worker 1 done
Worker 2 done
Worker 3 done
Worker 4 done
Worker 5 done

Note that the order of workers starting and finishing may vary between runs due to the nature of concurrent execution.

This approach provides a way to wait for multiple threads to finish their work. However, it doesn’t provide a straightforward way to propagate errors from workers. For more advanced use cases, you might need to implement additional error handling mechanisms.