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 sequenceseach with its own generator variableusing the same instance of the package.
The standard provides three ways to initialize a pseudo-random sequence:
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) simultaneouslyfor example, another call arrives while the first callers rendezvous is being executedthe 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 |