Mutexes in Ada
In the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a mutex to safely access data across multiple tasks.
with Ada.Text_IO;
with Ada.Containers.Indefinite_Hashed_Maps;
with Ada.Strings.Hash;
with Ada.Task_Identification;
with Ada.Synchronous_Task_Control;
procedure Mutexes is
package String_Integer_Maps is new Ada.Containers.Indefinite_Hashed_Maps
(Key_Type => String,
Element_Type => Integer,
Hash => Ada.Strings.Hash,
Equivalent_Keys => "=");
-- Container holds a map of counters; since we want to
-- update it concurrently from multiple tasks, we
-- add a Protected_Type to synchronize access.
protected type Container is
procedure Inc (Name : String);
function Get_Counters return String_Integer_Maps.Map;
private
Counters : String_Integer_Maps.Map;
end Container;
protected body Container is
procedure Inc (Name : String) is
begin
if Counters.Contains (Name) then
Counters (Name) := Counters (Name) + 1;
else
Counters.Insert (Name, 1);
end if;
end Inc;
function Get_Counters return String_Integer_Maps.Map is
begin
return Counters;
end Get_Counters;
end Container;
C : Container;
-- This task type increments a named counter in a loop
task type Incrementor (Name : access String; N : Integer);
task body Incrementor is
begin
for I in 1 .. N loop
C.Inc (Name.all);
end loop;
end Incrementor;
A1, A2 : Incrementor (new String'("a"), 10_000);
B : Incrementor (new String'("b"), 10_000);
begin
-- Wait for all tasks to complete
delay 1.0;
-- Print the final state of counters
declare
Final_Counters : constant String_Integer_Maps.Map := C.Get_Counters;
begin
for Cursor in Final_Counters.Iterate loop
Ada.Text_IO.Put_Line
(String_Integer_Maps.Key (Cursor) & ": " &
Integer'Image (String_Integer_Maps.Element (Cursor)));
end loop;
end;
end Mutexes;
This Ada program demonstrates the use of a protected type, which is Ada’s equivalent to a mutex. The Container
type uses a protected type to ensure thread-safe access to its internal map.
The program creates three tasks that concurrently increment counters in the shared Container
. Two tasks increment the “a” counter, and one task increments the “b” counter.
To run the program, save it as mutexes.adb
and compile it with an Ada compiler:
$ gnatmake mutexes.adb
$ ./mutexes
a: 20000
b: 10000
Running the program shows that the counters are updated as expected, demonstrating safe concurrent access to shared state using Ada’s protected types.
Next, we’ll look at implementing this same state management task using only tasks and other synchronization primitives in Ada.