Protected Objects & Types
Share data safely between tasks with protected objects: protected procedures (exclusive), functions (concurrent reads), entries with barriers (condition synchronization), protected types, the bounded-buffer pattern, and why protected objects replace mutexes and condition variables.
A protected object encapsulates shared data together with the only operations that may access it, and the runtime guarantees mutual exclusion. Entries with Boolean barriers add condition synchronization: a caller waits until the barrier is true, and barriers are re-evaluated automatically after every protected procedure or entry. This replaces error-prone mutex/condition-variable code with a declarative monitor.
Procedures and Functions
'protected Counter is procedure Increment; function Value return Natural; private Count : Natural := 0; end Counter;' declares a single protected object; 'protected type Buffer is ...' declares a type. Protected procedures have exclusive read-write access; protected functions have read-only access and may run concurrently with each other. Private data can only be reached through these operations. Protected operations should be short and must not block (no delay, no entry calls to other tasks — a 'potentially blocking operation' raises Program_Error when detected).
• procedure — exclusive read/write; function — concurrent read-only. • Data in the private part is reachable only via the operations. • Keep operations short and non-blocking.
Entries and Barriers
'entry Put (X : Integer) when Size < 4 is ...' — callers of Put wait while the barrier is False. After each protected procedure or entry completes, the runtime re-evaluates the barriers and serves waiting callers whose barriers became True before admitting new callers (the 'eggshell' model). Barriers usually reference only the protected object's private data. The bounded buffer (Put blocks when full, Get when empty) is the classic example and also exists ready-made as Ada.Containers.Bounded_Synchronized_Queues.
• entry E (...) when Barrier is ... — condition synchronization. • Barriers re-evaluated automatically; waiting callers served first. • Classic patterns: bounded buffer, semaphore, event, barrier.
Code Examples
Four tasks increment the protected counter 10 000 times each — always 40 000, with no lost updates. The producer puts 1.. 100 into a 4-slot buffer and the consumer sums them, both blocking automatically at the barriers. Output: Counter after 4 tasks x 10_000 increments: 40000 Consumer received sum 1.. 100 = 5050 protected body Logger is procedure Log (S: String) is begin delay 0.1; -- blocking inside a protected operation -- protected operations must not block; do slow work outside
Potentially blocking operations are a bounded error (Program_Error with checks).
Count: Natural:= 0; -- global, updated by many tasks protected Counter is procedure Increment;... end Counter;
Wrap shared state in a protected object.
entry Get (X: out Item) when Global_Flag is -- barrier on outside data entry Get (X: out Item) when Size > 0 is
Barriers are re-evaluated only when the protected object changes; depend on its own state.
Which protected operations may execute concurrently? A. Procedures B. Functions C. Entries D. None B. Functions (read-only).
What does 'entry Get when Size > 0' do when Size = 0? A. Raises an exception B. The caller waits until the barrier becomes true C. Returns immediately D. Busy-waits
Why is the counter always 40 000? A. Luck B. Protected procedures execute with mutual exclusion C. Tasks run sequentially D. The compiler removes the tasks
Which statement is not allowed inside a protected operation? A. Assignment B. delay C. if D. Loop B. delay is potentially blocking.
Write a protected counting semaphore with entry Acquire and procedure Release.
protected type Semaphore (Initial : Natural) is entry Acquire; procedure Release; private Count : Natural := Initial; end Semaphore; protected body Semaphore is entry Acquire when Count > 0 is begin Count := Count - 1; end Acquire; procedure Release is begin Count := Count + 1; end Release; end Semaphore;
The barrier Count > 0 blocks Acquire when no permits remain; Release makes the barrier true and a waiting caller is served automatically.
with Ada.Text_IO; use Ada.Text_IO;
procedure L54_Protected is
-- A protected object: mutual exclusion without explicit locks
protected Counter is
procedure Increment; -- exclusive read-write access
function Value return Natural; -- concurrent read-only access
private
Count : Natural := 0;
end Counter;
protected body Counter is
procedure Increment is
begin
Count := Count + 1;
end Increment;
function Value return Natural is (Count);
end Counter;
-- A protected type with entries and barriers: a bounded buffer
type Index is mod 4;
type Item_Array is array (Index) of Integer;
protected type Buffer is
entry Put (X : Integer); -- blocks while full
entry Get (X : out Integer); -- blocks while empty
private
Items : Item_Array;
Head, Tail : Index := 0;
Size : Natural := 0;
end Buffer;
protected body Buffer is
entry Put (X : Integer) when Size < 4 is -- barrier
begin
Items (Tail) := X;
Tail := Tail + 1; -- modular wrap-around
Size := Size + 1;
end Put;
…