AdaPower Logged in as Guest
Ada Tools and Resources

Ada 95 Reference Manual
Ada Source Code Treasury
Bindings and Packages
Ada FAQ


Join >
Articles >
Ada FAQ >
Getting Started >
Home >
Books & Tutorials >
Source Treasury >
Packages for Reuse >
Latest Additions >
Ada Projects >
Press Releases >
Ada Audio / Video >
Home Pages >
Links >
Contact >
About >
Login >
Back
I'm Queueless, or, Suspension Objects Instead of Entries (Matthew Heaney)

In previous articles we picked up chopsticks by waiting in the Pick_Up
entry queue of the Chopsticks protected object.  Here we implement the
Pick_Up operation as a protected procedure only, and use a suspension
object to keep the philosopher task waiting when chopsticks aren't
immediately available.

This new algorithm for picking up chopsticks is not unlike the classic
test-and-set loop used to claim ownership of a resource.  The task tries
to pick up the chopsticks.  If it was not successful, the task suspends
itself on a suspension object, and tries again later when its neighbor
puts down its chopsticks.

Implementation

The Pick_Up operation is implemented as a protected procedure that
returns a value indicating whether the operation was successful:

   protected Chopsticks is

      procedure Pick_Up
       (Id      : in     Philosopher_Id;
        Success :    out Boolean);

      ...

   end Chopsticks;


The philosopher task enters a loop in which it keeps trying to pick up
chopsticks, exiting when it has been successful:

   task body Philosopher_Task_Type is

      ...

      Picked_Up_Chopsticks : Boolean;

   begin

     ...

       loop

          Chopsticks.Pick_Up (Id, Picked_Up_Chopsticks);

          exit when Picked_Up_Chopsticks;

          Suspend_Until_True (Semaphore);

       end loop;


There is an array of suspension objects, one for each task:

   type Suspension_Object_Array is
      array (Philosopher_Id) of Suspension_Object;

   Semaphores : Suspension_Object_Array;


If the task is not immediately successful in picking up chopsticks, it
suspends itself by calling the Suspend_Until_True operation of the
semaphore assigned to that task.  It will try again later when its
neighbor puts her chopsticks down.

When a task puts chopsticks down, it marks the chopsticks as available,
and notifies its neighbors:

      procedure Put_Down
        (Id : in Philosopher_Id) is

         Prev : constant Philosopher_Id := (Id + 3) mod 5 + 1;
         Next : constant Philosopher_Id := Id mod 5 + 1;

      begin

         Available (Id) := True;
         Available (Next) := True;

         Set_True (Semaphores (Prev));
         Set_True (Semaphores (Next));

      end Put_Down;


Setting the semaphore to True wakes up the task suspended on the
semaphore.  The task will then try again to pick up the chopsticks.

If both chopsticks are available, the Pick_Up operation marks them as
now unavailable.  Otherwise, it just sets the calling task's semaphore
back to False:

      procedure Pick_Up
        (Id      : in     Philosopher_Id;
         Success :    out Boolean) is

         Next : constant Philosopher_Id := Id mod 5 + 1;

      begin

         if Available (Id) and Available (Next) then

            Available (Id) := False;
            Available (Next) := False;

            Success := True;

         else

            Set_False (Semaphores (Id));
            Success := False;

         end if;

      end Pick_Up;


If unable to pick up both chopsticks, the task will suspend when it
calls Suspend_Until_True, because the semaphore was set to False.

(Although the operations Set_True and Set_False are atomic operations,
those operations must be called by the protected object.  In between the
time the task tries unsuccessfully to pick up chopsticks and the time it
suspends itself, its neighbor may have put the chopsticks back down.
The task can't set the semaphore back to False itself, outside the
protected object, because that could clobber a signal just sent by the
neighbor.)

The last thing to mention concerns task initialization.  Recall that we
have to keep the tasks waiting following their activation, until the
package has been properly initialized.

In earlier implementations, the tasks waited for a signal sent by the
Start procedure.  The signal was implemented using a protected object.
However, we already have suspension objects, so we might as use them to
suspend the tasks during initialization too.

The first thing the task does when it activates is suspend on its
suspension object:

   task body Philosopher_Task_Type is

      ...

   begin

      Suspend_Until_True (Semaphore);

      ...

   end Philosopher_Task_Type;


Suspension objects are initialized to False, per RM95 D.10 (6), so the
task definitely will suspend.

Once the internal state has been updated, the Start procedure sets all
suspension objects to True, which resumes all the suspended tasks:


   procedure Start
     (Update : in Update_Access) is
   begin

      ...

      for Id in Philosopher_Id loop
         Set_True (Semaphores (Id));
      end loop;

   end Start;


Source code ready for GNAT Chop:

--STX
with Ada.Numerics.Float_Random;    use Ada.Numerics.Float_Random;
with Ada.Synchronous_Task_Control; use Ada.Synchronous_Task_Control;
with Ada.Text_IO;

package body Philosophers is

   Update : Update_Access;


   package Id_Management is

      function Get_Id return Philosopher_Id;

   end;
   use Id_Management;

   package body Id_Management is

      Id : Natural := 0;

      function Get_Id return Philosopher_Id is
      begin
         Id := Id + 1;
         return Id;
      end;

   end Id_Management;



   task type Philosopher_Task_Type (Id : Philosopher_Id := Get_Id);

   type Philosopher_Task_Array is
      array (Philosopher_Id) of Philosopher_Task_Type;

   Philosopher_Tasks : Philosopher_Task_Array;


   type Suspension_Object_Array is
      array (Philosopher_Id) of Suspension_Object;

   Semaphores : Suspension_Object_Array;


   type Duration_Array is
      array (Philosopher_Id) of Duration;

   Delay_Times : Duration_Array;


   protected Random_Delay is

      procedure Get (Id : in Philosopher_Id);

      procedure Initialize;

   private

      G : Generator;

   end Random_Delay;



   procedure Start
     (Update : in Update_Access) is
   begin

      Ada.Text_IO.Put_Line
        ("Using suspension objects instead of entry queue.");

      Philosophers.Update := Update;

      Random_Delay.Initialize;

      for Id in Philosopher_Id loop
         Set_True (Semaphores (Id));
      end loop;

   end Start;


   function Get_Delay (Id : Philosopher_Id) return Duration is
   begin
      return Delay_Times (Id);
   end;


   type Boolean_Array is array (Philosopher_Id) of Boolean;

   protected Chopsticks is

      procedure Pick_Up
       (Id      : in     Philosopher_Id;
        Success :    out Boolean);

      procedure Put_Down
        (Id : in Philosopher_Id);

   private

      Available : Boolean_Array := (others => True);

   end Chopsticks;



   task body Philosopher_Task_Type is

      Delay_Time : Duration renames Delay_Times (Id);

      Semaphore : Suspension_Object renames Semaphores (Id);

      Picked_Up_Chopsticks : Boolean;

   begin

      Suspend_Until_True (Semaphore);

      Update (Id, Breathing);

      for I in 1 .. 5 loop

         loop
            Update (Id, Picking_Up);
            Chopsticks.Pick_Up (Id, Picked_Up_Chopsticks);
            exit when Picked_Up_Chopsticks;
            Update (Id, Suspending);
            Suspend_Until_True (Semaphore);
         end loop;

         Random_Delay.Get (Id);
         Update (Id, Eating);
         delay Delay_Time;
         Update (Id, Done_Eating);
         Chopsticks.Put_Down (Id);

         Random_Delay.Get (Id);
         Update (Id, Thinking);
         delay Delay_Time;
         Update (Id, Done_Thinking);

      end loop;

      Update (Id, Dying);

      delay 60.0; -- hack to work around error in my RTS

   end Philosopher_Task_Type;


   protected body Chopsticks is

      procedure Pick_Up
        (Id      : in     Philosopher_Id;
         Success :    out Boolean) is

         Next : constant Philosopher_Id := Id mod 5 + 1;

      begin

         if Available (Id) and Available (Next) then

            Available (Id) := False;
            Available (Next) := False;

            Success := True;

         else

            Set_False (Semaphores (Id));
            Success := False;

         end if;

      end Pick_Up;


      procedure Put_Down
        (Id : in Philosopher_Id) is

         Prev : constant Philosopher_Id := (Id + 3) mod 5 + 1;
         Next : constant Philosopher_Id := Id mod 5 + 1;

      begin

         Available (Id) := True;
         Available (Next) := True;

         Set_True (Semaphores (Prev));
         Set_True (Semaphores (Next));

      end Put_Down;


   end Chopsticks;


   protected body Random_Delay is

      procedure Get (Id : in Philosopher_Id) is

         F : constant Float := Random (G);
      begin
         Delay_Times (Id) := 10 * Duration (F);
      end;

      procedure Initialize is
      begin
         Reset (G);
      end;

   end Random_Delay;


end Philosophers;

package Philosophers is

   pragma Elaborate_Body;

   subtype Philosopher_Id is Positive range 1 .. 5;

   type State_Type is
      (Breathing,
       Thinking,
       Done_Thinking,
       Picking_Up,
       Suspending,
       Eating,
       Done_Eating,
       Dying);

   type Update_Access is
      access procedure (Id    : in Philosopher_Id;
                        State : in State_Type);

   procedure Start (Update : in Update_Access);

   function Get_Delay (Id : Philosopher_Id) return Duration;

end Philosophers;


(c) 1998-2004 All Rights Reserved David Botton