Select in Pascal

Pascal doesn’t have built-in concurrency primitives like channels or goroutines, so we’ll simulate this behavior using threads and a custom channel-like structure.

program SelectExample;

uses
  SysUtils, Classes;

type
  TStringChannel = class
  private
    FValue: string;
    FReady: Boolean;
    FLock: TRTLCriticalSection;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Send(const AValue: string);
    function Receive(out AValue: string): Boolean;
  end;

  TWorkerThread = class(TThread)
  private
    FChannel: TStringChannel;
    FDelay: Integer;
    FMessage: string;
  protected
    procedure Execute; override;
  public
    constructor Create(AChannel: TStringChannel; ADelay: Integer; const AMessage: string);
  end;

constructor TStringChannel.Create;
begin
  inherited;
  InitializeCriticalSection(FLock);
  FReady := False;
end;

destructor TStringChannel.Destroy;
begin
  DeleteCriticalSection(FLock);
  inherited;
end;

procedure TStringChannel.Send(const AValue: string);
begin
  EnterCriticalSection(FLock);
  try
    FValue := AValue;
    FReady := True;
  finally
    LeaveCriticalSection(FLock);
  end;
end;

function TStringChannel.Receive(out AValue: string): Boolean;
begin
  EnterCriticalSection(FLock);
  try
    if FReady then
    begin
      AValue := FValue;
      FReady := False;
      Result := True;
    end
    else
      Result := False;
  finally
    LeaveCriticalSection(FLock);
  end;
end;

constructor TWorkerThread.Create(AChannel: TStringChannel; ADelay: Integer; const AMessage: string);
begin
  inherited Create(False);
  FChannel := AChannel;
  FDelay := ADelay;
  FMessage := AMessage;
  FreeOnTerminate := True;
end;

procedure TWorkerThread.Execute;
begin
  Sleep(FDelay);
  FChannel.Send(FMessage);
end;

var
  C1, C2: TStringChannel;
  Msg: string;
  I: Integer;

begin
  // For our example we'll select across two channels.
  C1 := TStringChannel.Create;
  C2 := TStringChannel.Create;

  try
    // Each channel will receive a value after some amount
    // of time, to simulate e.g. blocking RPC operations
    // executing in concurrent threads.
    TWorkerThread.Create(C1, 1000, 'one');
    TWorkerThread.Create(C2, 2000, 'two');

    // We'll use a loop to await both of these values
    // simultaneously, printing each one as it arrives.
    for I := 1 to 2 do
    begin
      repeat
        if C1.Receive(Msg) then
        begin
          WriteLn('received ', Msg);
          Break;
        end;
        if C2.Receive(Msg) then
        begin
          WriteLn('received ', Msg);
          Break;
        end;
        Sleep(10);  // Small delay to prevent busy waiting
      until False;
    end;
  finally
    C1.Free;
    C2.Free;
  end;
end.

This Pascal program simulates the behavior of the original code. Here’s how it works:

  1. We define a TStringChannel class to simulate channels. It uses a critical section for thread safety.

  2. The TWorkerThread class simulates goroutines. Each thread waits for a specified delay and then sends a message to its channel.

  3. In the main program, we create two channels and start two worker threads.

  4. We then use a loop to continuously check both channels for incoming messages. This simulates the select statement in the original code.

  5. When a message is received, it’s printed and the loop breaks to check the next message.

To run this program, save it as select_example.pas and compile it with a Pascal compiler that supports threads (like Free Pascal):

$ fpc select_example.pas
$ ./select_example
received one
received two

Note that the total execution time will be slightly over 2 seconds, as both the 1 and 2 second delays execute concurrently.

While this Pascal version doesn’t have the elegance of Go’s select statement, it demonstrates how to achieve similar functionality using threads and custom synchronization primitives.