Previous Table of Contents Next


10.4.4.7. Implementing the Random Number Generator

Examine the body of HB.Random_Task.

    1 with Ada.Numerics.Discrete_Random;
    2 package body HB.Random_Task is
    3
    4   task body Randomizer is
    5     package RandomTen is
    6       new Ada.Numerics.Discrete_Random
    7         (Result_Subtype => RandomRange);
    8     G: RandomTen.Generator;
    9   begin
   10     RandomTen.Reset(Gen => G);
   11     loop
   12       select
   13         accept GiveNumber (Result: out RandomRange) do
   14           Result := RandomTen.Random(Gen => G);
   15         end GiveNumber;
   16       or
   17         terminate;
   18       end select;
   19     end loop;
   20   end Randomizer;
   21
   22 end HB.Random_Task;

The context clause mentions Ada.Numerics.Discrete_Random. This standard generic library package can be instantiated for any integer or enumeration type or subtype; one could use it, for example, to produce random coin flips of the enumeration type (Heads, Tails). Here I instantiate it (lines 6-7) for the RandomRange of 1..10.

Using the random number generator requires declaring a generator variable, as I declare G in line 8. The type Generator is limited private; as such, it has no operations except those provided by the package. A generator variable retains the current seed of the number generator, so I can run multiple random number sequences—each with its own generator variable—using the same instance of the package.

The standard provides three ways to initialize a pseudo-random sequence:

  If I do nothing at all, the sequence is initialized with an unknown value that is the same for each initialization. This gives a repeatable pseudo-random sequence.
  If I call Reset as in line 10, the sequence is initialized with a value that depends on the current time of day. The pseudo-random sequence is then effectively random because I cannot predict this value, which will obviously be different in successive Reset calls.
  If I call Reset with two parameters, a generator variable and an integer value, I can make the sequence repeatable by using the same integer value in successive Reset calls.

If I had written lines 11-19 as

   loop
     accept GiveNumber (Result: out RandomRange) do
       Result := RandomTen.Random(Gen => G);
     end GiveNumber;
   end loop;

the task would loop repeatedly, waiting at the accept in each cycle, until another task called GiveNumber. The accept/do/end frame is called a rendezvous; while this task is executing the rendezvous code, the calling task is blocked and waiting for the result. Once the rendezvous is completed, both tasks become ready and each can run again when it gains control of a CPU.

The Ada runtime system provides a FIFO (first in, first out) queue for each entry of each task. If several other tasks call the entry (quasi) simultaneously—for example, another call arrives while the first caller’s rendezvous is being executed—the calls are queued in this entry queue. One call is accepted per cycle, so the queued caller is handled in the next cycle.

The simple loop above has no way to terminate, so if, at a given point, no further calls arrive, the task “hangs,” waiting at the accept for a call that will never come. The select statement that enclose the accept (lines 12-18) provides two select alternatives. The behavior of this strange-looking construct is that calls are accepted as long as they continue to arrive. However, if no calls are pending on the entry, and the other tasks in the program are ready to terminate, then this task terminates as well.

The terminate alternative provides a kind of graceful implicit termination of a task. A task also terminates if control passes to the end of its body, so I could use program logic to force this task out of its main loop. For example, I could write a finite one:

   while the time is earlier than 5 PM loop
     ...
   end loop;

and the task would terminate just after 5 p.m. The disadvantage here is that I would strand any pending calls. Other strategies are also possible; the programmer has substantial flexibility here.

10.4.4.8. Implementing the Screen Manager

Finally, I present the body of HB.Screen.

    1 with Ada.Text_IO, Ada.Integer_Text_IO;
    2 use  Ada.Text_IO, Ada.Integer_Text_IO;
    3 package body HB.Screen is
    4
    5   task body Manager is
    6   begin
    7     loop
    8       select
    9         accept Beep do
   10           Put (Item => ASCII.BEL);
   11           Ada.Text_IO.Flush;
   12         end Beep;
   13       or
   14         accept ClearScreen do
   15           Put (Item => ASCII.ESC & “[2J”);
   16           Ada.Text_IO.Flush;
   17         end ClearScreen;
   18       or
   19         accept Write
   20           (Item: in String; Where: in Position) do
   21           Put (Item => ASCII.ESC & ‘[‘);
   22           Put (Item => Where.Row, Width => 1);
   23           Put (Item => ‘;’);
   24           Put (Item => Where.Column, Width => 1);
   25           Put (Item => ‘f’);
   26           Put(Item => Item);
   27           Ada.Text_IO.Flush;
   28         end Write;
   29       or
   30         terminate;
   31       end select;
   32     end loop;
   33
   34   end Manager;
   35 end HB.Screen;

The task body for Manager is similar to that of the random number generator. It is intended to operate as a background task, similar to a device driver, and therefore is allowed to activate. It has a main loop, inside of which is a select statement. This statement is an example of nondeterministic selection and is based on the concurrent programming research of the 1970s, especially CSP and its later implementation in Occam.

In each cycle of the main loop, control reaches the select. If no calls are pending on any of the three entries, the task then waits at the select for one of these entries to be called and responds to the one that arrives first, entering a rendezvous with the calling task. The Put statements in the rendezvous blocks are generally obvious; Ada.Text_IO.Flush is used to send the OS output buffer contents directly to the terminal. As before, the calling task blocks during the rendezvous, and furthermore Manager can take no other action until the rendezvous completes. This ensures that the screen transaction can be fully sent to the terminal before another begins and prevents the messed-up screen I described earlier.


Previous Table of Contents Next