Waitgroups in Ada

Our example demonstrates how to wait for multiple tasks to finish using a Protected_Counter. This is similar to the concept of wait groups in other languages.

with Ada.Text_IO;
with Ada.Task_Identification;
with Ada.Calendar;

procedure Waitgroups is
   use Ada.Text_IO;
   use Ada.Calendar;

   protected type Protected_Counter is
      procedure Increment;
      procedure Decrement;
      entry Wait;
   private
      Count : Natural := 0;
   end Protected_Counter;

   protected body Protected_Counter is
      procedure Increment is
      begin
         Count := Count + 1;
      end Increment;

      procedure Decrement is
      begin
         Count := Count - 1;
      end Decrement;

      entry Wait when Count = 0 is
      begin
         null;
      end Wait;
   end Protected_Counter;

   Counter : Protected_Counter;

   task type Worker is
      entry Start(ID : Positive);
   end Worker;

   task body Worker is
      Worker_ID : Positive;
   begin
      accept Start(ID : Positive) do
         Worker_ID := ID;
      end Start;

      Put_Line("Worker" & Worker_ID'Image & " starting");

      delay 1.0;  -- Simulate an expensive task

      Put_Line("Worker" & Worker_ID'Image & " done");

      Counter.Decrement;
   end Worker;

   Workers : array (1..5) of Worker;

begin
   for I in Workers'Range loop
      Counter.Increment;
      Workers(I).Start(I);
   end loop;

   Counter.Wait;
end Waitgroups;

This is the equivalent of the worker function in Ada. It’s implemented as a task type.

The Protected_Counter type is used to safely manage the count of active workers. It provides thread-safe operations to increment, decrement, and wait for the count to reach zero.

In the main procedure:

We create an array of Worker tasks and start them, incrementing the counter for each.

for I in Workers'Range loop
   Counter.Increment;
   Workers(I).Start(I);
end loop;

We then wait for all workers to finish by calling Counter.Wait. This will block until all workers have decremented the counter.

Counter.Wait;

Note that Ada’s tasking model inherently handles the termination of all tasks before the main program exits, so we don’t need to explicitly wait for task completion beyond this point.

To run the program, save it as waitgroups.adb and use the following commands:

$ gnatmake waitgroups.adb
$ ./waitgroups
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

The order of workers starting up and finishing is likely to be different for each invocation.

Ada’s tasking model provides built-in support for concurrent programming, making it well-suited for this kind of parallel execution pattern. The Protected_Counter type ensures thread-safe operations, similar to the sync.WaitGroup in other languages.