Guarded Data Structures
Suppose we want to purge a concurrent stack, doing something with each
item as it is popped, like this:
while not Is_Empty (Stack) loop
declare
Item : constant T := Get_Top (Stack);
begin
Do_Something_To (Item);
Pop (Stack);
end;
end loop;
In our earlier implementation of the stack, we used a semaphore to
synchronize access to the stack for every operation. Using that
implementation here would not be very efficient, since multiple
operations are called during every pass through the loop.
What we'd rather do is claim exclusive access to the stack just once,
before the loop, and then release the stack when we're done iterating.
Inside the loop no further synchronization would be necessary.
A "guarded" data structure is a generalization of a semaphore, with
operations to seize and release the structure. Using a guard is a more
efficient way to manipulate a structure when operations need to be
called in batch:
Seize (Structure);
<do a bunch of stuff to the structure>
Release (Structure);
Implementation
Let's assemble a guarded stack of integers from reusable components. At
the end of the day what we want is a package that looks like this:
package Integer_Stacks is
type Integer_Stack is ...;
procedure Seize (Stack : in out Integer_Stack);
procedure Release (Stack : in out Integer_Stack);
procedure Push (Item : in Integer;
On : in out Integer_Stack);
function Get_Top (Stack : Integer_Stack) return Integer;
...
end Integer_Stacks;
We start by instantiating a bounded sequential stack:
generic
type Item_Type is private;
Max_Depth : in Positive;
package Stacks is
type Stack_Type is tagged limited private;
procedure Push
(Item : in Item_Type;
On : in out Stack_Type);
...
end Stacks;
package Integer_Stacks is
package Stack_Types is
new Stacks (Integer, Max_Depth => 10);
...
end Integer_Stacks;
We implement the guarded type using mixin inheritance. Per that idiom,
we derive from a generic formal type, and extend it with a semaphore
component:
generic
type Resource_Type (<>) is abstract tagged limited private;
package Guard_Mixin is
type Guarded_Resource is
new Resource_Type with record
Semaphore : Semaphore_Type;
end record;
procedure Seize (Resource : in out Guarded_Resource);
procedure Release (Resource : in out Guarded_Resource);
end Guard_Mixin;
The semaphore is a public attribute, to allow clients to make timed or
conditional entry calls. We also provide explicit Seize and Release
operations, for clients who would rather use parameter (instead of
prefix) notation.
We now take our sequential stack type, and mix-in a guard:
package Integer_Stacks is
package Stack_Types is
new Stacks (Integer, Max_Depth => 10);
package Guarded_Stacks is
new Guard_Mixin (Stack_Types.Stack_Type);
...
end Integer_Stacks;
This gives us type Integer_Stacks.Guarded_Stacks.Guarded_Resource, which
is not quite what we want. We also want stack operations to be directly
visible from Integer_Stacks, not from one of its nested packages.
So we make one more derivation:
package Integer_Stacks is
package Stack_Types is new Stacks (Integer, Max_Depth => 10);
package Guarded_Stacks is new Guard_Mixin (Stack_Types.Stack_Type);
type Integer_Stack is
new Guarded_Stacks.Guarded_Resource with null record;
...
end Integer_Stacks;
Deriving from a type in an inner package, in order to make its
operations directly visible from the outer package, is called
"transitivity of visibility."
There's one more declaration we need to make. The original stack
package provides a passive iterator:
generic
with procedure Process
(Item : in out Item_Type;
Done : in out Boolean);
procedure For_Every_Item (Stack : in out Stack_Type'Class);
What we'd like is for clients to have visibility to this generic
procedure directly from the Integer_Stacks package. We can effect this
by using a generic renaming declaration:
generic procedure For_Every_Item renames
Stack_Types.For_Every_Item;
Note carefully that the stack parameter in the iterator is declared as
Stack_Type'Class. Generic operations aren't primitive and therefore
don't get inherited, so you have to make the parameter class-wide, in
order for the procedure to work for any type in the derivation tree.
The completed spec looks like this:
package Integer_Stacks is
package Stack_Types is
new Stacks (Integer, Max_Depth => 10);
package Guarded_Stacks is
new Guard_Mixin (Stack_Types.Stack_Type);
type Integer_Stack is
new Guarded_Stacks.Guarded_Resource with null record;
generic procedure For_Every_Item renames
Stack_Types.For_Every_Item;
end Integer_Stacks;
A guarded component is error prone for the same reason a semaphore is:
it's really easy to not release the structure after you've seized it.
We use the same technique as we did earlier, and that's to declare an
object that releases the guard automatically during its finalization.
What we do here is a little different, though, because the type being
controlled is the result of an instantiation of a generic.
We import the guard type as a generic formal type, and import the Seize
and Release operations as generic formal parameters:
generic
type Guarded_Type (<>) is limited private;
with procedure Seize (Guarded : in out Guarded_Type) is <>;
with procedure Release (Guarded : in out Guarded_Type) is <>;
package Guarded_Controls is
type Guarded_Control (Guarded : access Guarded_Type) is
limited private;
private
...
end Guarded_Controls;
This package will work for any type. And if operations named Seize and
Release are directly visible at the point of instantiation, then you
don't even have to list them as generic actuals.
The type is implemented by deriving from Limited_Controlled and
overriding Initialize and Finalize:
private
use Ada.Finalization;
type Guarded_Control (Guarded : access Guarded_Type) is
new Limited_Controlled with null record;
procedure Initialize (Control : in out Guarded_Control);
procedure Finalize (Control : in out Guarded_Control);
end Guarded_Controls;
The control type just calls the Seize and Release operations of the
object designated by its access discriminant:
package body Guarded_Controls is
procedure Initialize (Control : in out Guarded_Control) is
begin
Seize (Control.Guarded.all); <--
end;
procedure Finalize (Control : in out Guarded_Control) is
begin
Release (Control.Guarded.all); <--
end;
end Guarded_Controls;
The guarded control type for guarded integer stacks is provided by an
instantiation of the generic guarded control package, declared as a
child of Integer_Stacks:
with Guarded_Controls;
package Integer_Stacks.Controls is
new Guarded_Controls (Integer_Stack);
Note how we don't have to explicitly supply Seize and Release as generic
actual subprograms, because they are directly visible at the point of
instantiation.
Now, finally, we can iterate over the stack, efficiently, with exclusive
access:
Stack : aliased Integer_Stack;
...
declare
Control : Guarded_Control (Stack'Access);
begin
while not Is_Empty (Stack) loop
Put (Integer'Image (Get_Top (Stack)));
Pop (Stack);
end loop;
New_Line;
end;
Matt
<mailto:matthew_heaney@acm.org>
--STX
package body Binary_Semaphores is
protected body Semaphore_Type is
procedure Release is
begin
In_Use := False;
end;
entry Seize when not In_Use is
begin
In_Use := True;
end;
end Semaphore_Type;
end Binary_Semaphores;
package Binary_Semaphores is
pragma Pure;
protected type Semaphore_Type is
procedure Release;
entry Seize;
private
In_Use : Boolean := False;
end Semaphore_Type;
end Binary_Semaphores;
package body Guard_Mixin is
procedure Seize (Resource : in out Guarded_Resource) is
begin
Resource.Semaphore.Seize;
end;
procedure Release (Resource : in out Guarded_Resource) is
begin
Resource.Semaphore.Release;
end;
end Guard_Mixin;
with Binary_Semaphores; use Binary_Semaphores;
generic
type Resource_Type (<>) is abstract tagged limited private;
package Guard_Mixin is
type Guarded_Resource is
new Resource_Type with record
Semaphore : Semaphore_Type;
end record;
procedure Seize (Resource : in out Guarded_Resource);
procedure Release (Resource : in out Guarded_Resource);
end Guard_Mixin;
package body Guarded_Controls is
procedure Initialize (Control : in out Guarded_Control) is
begin
Seize (Control.Guarded.all);
end;
procedure Finalize (Control : in out Guarded_Control) is
begin
Release (Control.Guarded.all);
end;
end Guarded_Controls;
with Ada.Finalization;
generic
type Guarded_Type (<>) is limited private;
with procedure Seize (Guarded : in out Guarded_Type) is <>;
with procedure Release (Guarded : in out Guarded_Type) is <>;
package Guarded_Controls is
type Guarded_Control (Guarded : access Guarded_Type) is
limited private;
private
use Ada.Finalization;
type Guarded_Control (Guarded : access Guarded_Type) is
new Limited_Controlled with null record;
procedure Initialize (Control : in out Guarded_Control);
procedure Finalize (Control : in out Guarded_Control);
end Guarded_Controls;
with Guarded_Controls;
package Integer_Stacks.Controls is
new Guarded_Controls (Integer_Stack);
with Stacks;
with Guard_Mixin;
pragma Elaborate (Stacks);
pragma Elaborate (Guard_Mixin);
package Integer_Stacks is
pragma Pure;
package Stack_Types is
new Stacks (Integer, Max_Depth => 10);
package Guarded_Stacks is
new Guard_Mixin (Stack_Types.Stack_Type);
type Integer_Stack is
new Guarded_Stacks.Guarded_Resource with null record;
generic procedure For_Every_Item renames
Stack_Types.For_Every_Item;
end Integer_Stacks;
with Integer_Stacks; use Integer_Stacks;
package P is
Stack : aliased Integer_Stack;
end;
package body Stacks is
procedure Push
(Item : in Item_Type;
On : in out Stack_Type) is
begin
On.Top := On.Top + 1;
On.Items (On.Top) := Item;
end;
procedure Pop
(Stack : in out Stack_Type) is
begin
Stack.Top := Stack.Top - 1;
end;
function Get_Top
(Stack : Stack_Type) return Item_Type is
begin
return Stack.Items (Stack.Top);
end;
procedure Set_Top
(Stack : in out Stack_Type;
Item : in Item_Type) is
begin
Stack.Items (Stack.Top) := Item;
end;
function Is_Empty (Stack : Stack_Type) return Boolean is
begin
return Stack.Top = 0;
end;
procedure For_Every_Item (Stack : in out Stack_Type'Class) is
Done : Boolean := False;
begin
for I in reverse 1 .. Stack.Top loop
Process (Stack.Items (I), Done);
exit when Done;
end loop;
end For_Every_Item;
end Stacks;
generic
type Item_Type is private;
Max_Depth : in Positive;
package Stacks is
pragma Preelaborate;
type Stack_Type is tagged limited private;
procedure Push
(Item : in Item_Type;
On : in out Stack_Type);
procedure Pop
(Stack : in out Stack_Type);
function Get_Top
(Stack : Stack_Type) return Item_Type;
procedure Set_Top
(Stack : in out Stack_Type;
Item : in Item_Type);
function Is_Empty (Stack : Stack_Type) return Boolean;
generic
with procedure Process
(Item : in out Item_Type;
Done : in out Boolean);
procedure For_Every_Item (Stack : in out Stack_Type'Class);
private
subtype Item_Array_Range is Positive range 1 .. Max_Depth;
type Item_Array is array (Item_Array_Range) of Item_Type;
type Stack_Type is
tagged limited record
Items : Item_Array;
Top : Natural := 0;
end record;
end Stacks;
with Integer_Stacks.Controls;
use Integer_Stacks, Integer_Stacks.Controls;
with P; use P;
with Ada.Text_IO; use Ada.Text_IO;
procedure Test_Stacks is
begin
declare
Control : Guarded_Control (Stack'Access);
begin
Push (10, On => Stack);
Push (9, On => Stack);
Push (8, On => Stack);
end;
declare
procedure Put_Item
(Item : in out Integer;
Done : in out Boolean) is
begin
Put (Integer'Image (Item));
end;
procedure Put_Items is
new For_Every_Item (Put_Item);
Control : Guarded_Control (Stack'Access);
begin
Put_Items (Stack);
New_Line;
end;
declare
Control : Guarded_Control (Stack'Access);
begin
while not Is_Empty (Stack) loop
Put (Integer'Image (Get_Top (Stack)));
Pop (Stack);
end loop;
New_Line;
end;
end Test_Stacks;
Contributed by: Matthew Heaney
Contributed on: May 24, 1999
License: Public Domain
Back