Ada: Tasking

Concurrency is part of the Ada language, not a library: a task is a thread declared in the source, an entry is a synchronized call, and a protected object is a monitor with compiler-checked mutual exclusion. This is why Ada dominates real-time and safety-critical domains.

Tasks

A task type declares a thread. The body runs concurrently from the moment its declaration is elaborated; the program cannot end while tasks run:

with Ada.Text_IO; use Ada.Text_IO;

procedure Task_Demo is
   task Worker;
   task body Worker is
   begin
      for I in 1 .. 3 loop
         Put_Line ("worker tick" & Integer'Image (I));
         delay 0.1;              -- suspend this task for 0.1 seconds
      end loop;
   end Worker;
begin
   Put_Line ("main continues in parallel");
end Task_Demo;
--  both texts interleave; the run time ends when Worker finishes

delay suspends the calling task — at least the requested time. Tasks may also be declared as task type and instantiated many times, with a discriminant configuring each.

Rendezvous with Entries

An entry is a subprogram call that synchronizes: the caller blocks until the accepting task reaches the matching accept. Data crosses in both directions inside the rendezvous:

Producer calling an entry, blocking until the consumer's accept statement
Fig. 1 — The entry call blocks; the rendezvous runs the critical section atomically; both tasks resume.
procedure Rendezvous_Demo is
   task type Counter (Max : Natural);
   task body Counter is
      N : Natural := 0;
   begin
      loop
         accept Bump do                  -- waits for a caller
            N := N + 1;                  -- runs inside the rendezvous
         end Bump;
         exit when N >= Max;
      end loop;
   end Counter;

   C : Counter (Max => 5);
begin

Protected Objects

For shared data, a protected object is simpler and faster than hand-rolled rendezvous: its procedures run under mutual exclusion, its entries add conditions, its functions run concurrently read-only:

protected Buffer is
   entry Put (Item : in Integer);
   entry Get (Item : out Integer);
private
   Cells : array (1 .. 8) of Integer;
   Count : Natural := 0;
end Buffer;

protected body Buffer is
   entry Put (Item : in Integer) when Count < 8 is
   begin
      Count := Count + 1;
      Cells (Count) := Item;     -- mutual exclusion is guaranteed
   end Put;

   entry Get (Item : out Integer) when Count > 0 is
   begin
      Item := Cells (Count);
      Count := Count - 1;
   end Get;
end Buffer;

The when guard is a barrier: callers of Put queue until there is room, callers of Get until there is data — a correct bounded queue in twenty lines, no mutexes, no condition variables, no race conditions.

select and Timeouts

select lets a task wait on several entries at once, with alternatives, guards, and timeouts — the shape of every robust real-time loop:

   --  inside a task body:
   --  loop
   --     select
   --        accept Shutdown do Running := False; end Shutdown;
   --     or
   --        accept Work (Item : in Integer) do Process (Item); end Work;
   --     or
   --        delay 5.0;                 -- nothing arrived in 5 seconds
   --        Put_Line ("still alive");
   --     end select;
   --     exit when not Running;
   --  end loop;

The Ravenscar profile restricts tasking to a statically analyzable subset (no dynamic task creation, simple barriers only) — the foundation of certified real-time systems and most SPARK concurrency proofs.

Next: Standard Library.

null; -- callers elsewhere: C.Bump; — blocks until C reaches accept end Rendezvous_Demo;