Stateful Goroutines in Ada

Our example demonstrates state management using tasks (Ada’s equivalent to goroutines) and protected objects for synchronization. This approach aligns with Ada’s built-in concurrency features and the principle of sharing data through communication.

with Ada.Text_IO;
with Ada.Numerics.Discrete_Random;
with Ada.Calendar;
with System.Atomic_Counters;

procedure Stateful_Tasks is
   package IO renames Ada.Text_IO;
   
   type Key_Type is range 0 .. 4;
   type Value_Type is range 0 .. 99;
   
   protected type State_Manager is
      procedure Read (Key : in Key_Type; Value : out Value_Type);
      procedure Write (Key : in Key_Type; Value : in Value_Type);
   private
      State : array (Key_Type) of Value_Type := (others => 0);
   end State_Manager;
   
   protected body State_Manager is
      procedure Read (Key : in Key_Type; Value : out Value_Type) is
      begin
         Value := State(Key);
      end Read;
      
      procedure Write (Key : in Key_Type; Value : in Value_Type) is
      begin
         State(Key) := Value;
      end Write;
   end State_Manager;
   
   Manager : State_Manager;
   
   Read_Ops, Write_Ops : System.Atomic_Counters.Atomic_Counter;
   
   task type Reader;
   task type Writer;
   
   task body Reader is
      package Random_Key is new Ada.Numerics.Discrete_Random(Key_Type);
      Gen : Random_Key.Generator;
      Key : Key_Type;
      Value : Value_Type;
   begin
      Random_Key.Reset(Gen);
      loop
         Key := Random_Key.Random(Gen);
         Manager.Read(Key, Value);
         System.Atomic_Counters.Increment(Read_Ops);
         delay 0.001;
      end loop;
   end Reader;
   
   task body Writer is
      package Random_Key is new Ada.Numerics.Discrete_Random(Key_Type);
      package Random_Value is new Ada.Numerics.Discrete_Random(Value_Type);
      Key_Gen : Random_Key.Generator;
      Value_Gen : Random_Value.Generator;
      Key : Key_Type;
      Value : Value_Type;
   begin
      Random_Key.Reset(Key_Gen);
      Random_Value.Reset(Value_Gen);
      loop
         Key := Random_Key.Random(Key_Gen);
         Value := Random_Value.Random(Value_Gen);
         Manager.Write(Key, Value);
         System.Atomic_Counters.Increment(Write_Ops);
         delay 0.001;
      end loop;
   end Writer;
   
   Readers : array (1 .. 100) of Reader;
   Writers : array (1 .. 10) of Writer;
   
begin
   delay 1.0;  -- Let tasks run for a second
   
   IO.Put_Line("Read Ops: " & System.Atomic_Counters.Value(Read_Ops)'Image);
   IO.Put_Line("Write Ops: " & System.Atomic_Counters.Value(Write_Ops)'Image);
end Stateful_Tasks;

In this Ada example, we use a protected object State_Manager to manage the shared state. This ensures that the data is never corrupted with concurrent access. The Read and Write procedures of the protected object encapsulate the requests to read or write the state.

We create 100 reader tasks and 10 writer tasks. Each task performs operations in a loop, using Ada’s random number generation to select keys and values. The System.Atomic_Counters package is used to safely count the number of operations performed.

The main procedure lets the tasks run for one second, then reports the number of read and write operations performed.

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

$ gnatmake stateful_tasks
$ ./stateful_tasks

Running this program will show the number of read and write operations completed in one second. The exact numbers will vary, but you might see output similar to this:

Read Ops:  71708
Write Ops:  7177

This Ada implementation demonstrates state management using tasks and protected objects, which are Ada’s built-in mechanisms for concurrent programming. While the approach is different from Go’s goroutines and channels, it achieves the same goal of safe concurrent access to shared state.