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.