7.3 Task synchronisation (the rendezvous).

The advantage of Ada tasking is that the Ada task model provides much more than the task as a thread, it offers us a model for IPC as well. Specifically the Ada tasking model defines methods for inter-task co-operation and much more in a system independent way using constructs known as entries.

A Rendezvous is just what it sounds like, a meeting place where two tasks arrange to meet up, if one task reaches it first then it waits for the other to arrive. The tasks rendezvous at an entry construct declared by the task; the task does not care who meets it there, it simply announces its intention to wait for someone.

In actual fact a FIFO queue is formed for each rendezvous of all tasks waiting, this can even be queried by clients to decide whether to call a rendezvous, or by rendezvous themselves.

7.3.1 Entry and Accept.

The entry is a construct used in a task specification to declare a rendezvous.

task type X is

   entry A_Rendezvous;
end X;

An entry is a little like a procedure, it may take parameters but may not return values, and is matched in the task body by an accept. The only items which may appear in a task specification are entries no subprograms or objects and although a private part is allowed it too must only contain entries.

task body X is
   ..
begin
   accept A_Rendezvous do
      ..
   end A_Rendezvous;
end X;

The accept body appears within the statement part of the task body for reasons which will become clear as we continue.

Consider the example below, a system of some sort has a cache of elements, it requests an element from the cache, if it is not in the cache then the cache itself reads an element from the master set. If this process of reading from the master fills the cache then it must be reordered. When the process finishes with the item it calls PutBack which updates the cache and if required updates the master.

task type Cached_Items is

   entry Request(Item : out Item_Type);
   entry PutBack(Item : in Item_Type);
end Cached_Items;

task body Cached_Items is

   Log_File : Ada.Text_IO.File_Type;
begin
   -- open the log file.
   loop
      accept Request(Item : out Item_Type) do
         -- satisfy from cache or get new.
      end Request;

      -- if had to get new, then check cache
      -- for overflow and consistency.

      accept PutBack(Item : in Item_Type) do
         -- replace item in cache.
      end PutBack;

      -- if item put back has changed
      -- then possibly update original.
   end loop;
end Cached_Items;

Below is some typical client code using the task type above.

declare
   Cache : Cached_Items;
   Item  : Item_Type;
begin
   Cache.Request(Item);
   ..
   Cache.PutBack(Item);
end;

It is the sequence of processing which is important here, Firstly the client task (remember, even if the client is the main program it is still, logically, a task) creates the cache task which executes its body. The first thing the cache does is some procedural code, its initialisation, in this case to open its log file. Next we have an accept statement, this is a rendezvous, and in this case the two parties are the owner task, when it reaches the keyword accept and the client task that calls Cache.Request(Item).

If the client task calls Request before the owner task has reached the accept then the client task will wait for the owner task. However we would not expect the owner task to take very long to open a log file, so it is more likely that it will reach the accept first and wait for a client task.

When both client and owner tasks are at the rendezvous then the owner task executes the accept code while the client task waits. When the owner task reaches the end of the rendezvous both the owner and the client are set off again on their own way.

If you look closely at the Cached_Items task body you can see why the accept bodies are within the statement part of the task, we have enclosed the two accepts within a loop. If we had not then after the rendezvous PutBack the task would have completed.

7.3.2 Select.

If we look closely at Cached_Items you might notice that if the client task calls Request twice in a row then you have a deadly embrace, the owner task cannot get to Request before executing PutBack and the client task cannot execute PutBack until it has satisfied the second call to Request.

To get around this problem we use a select statement which allows the task to specify a number of entry points which are valid at any time.

task body Cached_Items is

   Log_File : Ada.Text_IO.File_Type;
begin
   accept Request(Item : Item_Type) do
      -- open the log file.
      -- satisfy from cache or get new.
   end Request;

   loop
      select
         accept PutBack(Item : Item_Type) do
            -- replace item in cache.
         end PutBack;

         -- if item put back has changed
         -- then possibly update original.
      or
         accept Request(Item : Item_Type) do
            -- satisfy from cache or get new.
         end Request;

         -- if had to get new, then quickly
         -- check cache for overflow.
      end select;
   end loop;
end Cached_Items;

We have done two major things, first we have added the select construct which says that during the loop a client may call either of the entry points (In effect the select becomes the rendezvous waiting for either PutBack or Request). The second point is that we moved a copy of the entry Request into the initialisation section of the task so that we must call Request before anything else (also by moving the open log file code into this rendezvous it is only ever done if the task is used, if we create a task then don't use it we don't have to worry about opening the log file). It is worth noting that we can have many entry points with the same name and they may be the same or may do something different but we only need one entry in the task specification, this is an important point, so far we have not changed the specification of the task at all.


Previous PageContents PageNext Page

Copyright © 1996 Simon Johnston &
Addison Wesley Longman