Atomic Counters in Ada

Our primary mechanism for managing state in Ada is through protected objects. However, there are other options for managing state. Here we’ll look at using Ada’s atomic operations for atomic counters accessed by multiple tasks.

with Ada.Text_IO;
with System.Atomic_Counters;
with Ada.Task_Identification;

procedure Atomic_Counters is
   use Ada.Text_IO;
   use System.Atomic_Counters;

   -- We'll use an atomic counter type to represent our (always-positive) counter.
   Ops : Atomic_Unsigned := 0;

   -- We'll start 50 tasks that each increment the counter exactly 1000 times.
   task type Worker;

   task body Worker is
   begin
      for I in 1 .. 1000 loop
         -- To atomically increment the counter we use Increment.
         Increment (Ops);
      end loop;
   end Worker;

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

begin
   -- Wait until all the tasks are done.
   for W of Workers loop
      while not W'Terminated loop
         delay 0.01;
      end loop;
   end loop;

   -- Here no tasks are writing to 'Ops', but it's safe to atomically read
   -- a value even while other tasks might be (atomically) updating it.
   Put_Line ("ops:" & Atomic_Unsigned'Image (Ops));
end Atomic_Counters;

We expect to get exactly 50,000 operations. Had we used a non-atomic integer and incremented it with regular addition, we’d likely get a different number, changing between runs, because the tasks would interfere with each other. Moreover, we’d get race conditions that could lead to unpredictable behavior.

To compile and run this Ada program:

$ gnatmake atomic_counters.adb
$ ./atomic_counters
ops: 50000

Next, we’ll look at protected objects, another tool for managing state in Ada.