Worker Pools in Ada

Our example demonstrates how to implement a worker pool using tasks and protected objects in Ada.

with Ada.Text_IO;
with Ada.Calendar;

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

   Job_Count : constant Integer := 5;

   protected type Job_Queue is
      entry Add_Job(J : in Integer);
      entry Get_Job(J : out Integer);
      procedure Close;
   private
      Jobs : array (1..Job_Count) of Integer;
      Count : Natural := 0;
      Closed : Boolean := False;
   end Job_Queue;

   protected body Job_Queue is
      entry Add_Job(J : in Integer) when Count < Job_Count is
      begin
         Count := Count + 1;
         Jobs(Count) := J;
      end Add_Job;

      entry Get_Job(J : out Integer) when (Count > 0) or Closed is
      begin
         if Count = 0 then
            raise Program_Error with "Queue is empty and closed";
         end if;
         J := Jobs(1);
         Jobs(1..Count-1) := Jobs(2..Count);
         Count := Count - 1;
      end Get_Job;

      procedure Close is
      begin
         Closed := True;
      end Close;
   end Job_Queue;

   protected type Result_Queue is
      procedure Add_Result(R : in Integer);
      entry Get_Result(R : out Integer);
   private
      Results : array (1..Job_Count) of Integer;
      Count : Natural := 0;
   end Result_Queue;

   protected body Result_Queue is
      procedure Add_Result(R : in Integer) is
      begin
         Count := Count + 1;
         Results(Count) := R;
      end Add_Result;

      entry Get_Result(R : out Integer) when Count > 0 is
      begin
         R := Results(1);
         Results(1..Count-1) := Results(2..Count);
         Count := Count - 1;
      end Get_Result;
   end Result_Queue;

   Jobs : Job_Queue;
   Results : Result_Queue;

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

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

      loop
         select
            Jobs.Get_Job(Job);
            Put_Line("worker" & Integer'Image(Worker_ID) & " started  job" & Integer'Image(Job));
            delay 1.0;  -- Simulate an expensive task
            Put_Line("worker" & Integer'Image(Worker_ID) & " finished job" & Integer'Image(Job));
            Results.Add_Result(Job * 2);
         or
            terminate;
         end select;
      end loop;
   end Worker;

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

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

   for J in 1..Job_Count loop
      Jobs.Add_Job(J);
   end loop;
   Jobs.Close;

   declare
      Result : Integer;
   begin
      for A in 1..Job_Count loop
         Results.Get_Result(Result);
      end loop;
   end;
end Worker_Pools;

This Ada program implements a worker pool using tasks and protected objects. Here’s a breakdown of the main components:

  1. We define a Job_Queue protected type to safely manage job distribution among workers.

  2. A Result_Queue protected type is used to collect results from workers.

  3. The Worker task type represents our workers. Each worker continuously tries to get a job, process it (simulated by a 1-second delay), and add the result to the result queue.

  4. In the main procedure, we create 3 worker tasks, add 5 jobs to the job queue, and then collect all the results.

To run this program, save it as worker_pools.adb and compile it using an Ada compiler:

$ gnatmake worker_pools.adb
$ ./worker_pools

The output will show the 5 jobs being executed by various workers. The program takes about 2 seconds to complete despite doing about 5 seconds of total work because there are 3 workers operating concurrently.

This example demonstrates Ada’s built-in support for concurrent programming using tasks and protected objects, which provide a safe and structured way to implement worker pools and other concurrent patterns.