Previous | Table of Contents | Next |
10.4.4.2. A Concurrent Application
Consider a program containing three independent subtasks, A, B, and C, which are required to execute (or, on a uniprocessor machine, appear to execute) simultaneously. Each subtask executes a given number of cycles; during each cycle, each program rests (idles or sleeps) a random number of seconds. View the terminal screen as divided vertically into three equal-width windows; as each subtask reaches a nap period, it displays its state in its own window.
Because different subtasks are sleeping for different time periods, each subtask must control its own window and be able to write into it without interference from the other subtasks. The output from a typical execution might be
A nap 7 secs B nap 9 secs C nap 5 secs A nap 10 secs B nap 1 secs C nap 3 secs A nap 1 secs B nap 1 secs C nap 1 secs A nap 6 secs B nap 10 secs C nap 7 secs A nap 1 secs B nap 3 secs B nap 10 secs B nap 1 secs
This program is skeletal without much real functionality; its purpose is to illustrate the Ada concurrency model concisely. The model is very rich, providing a number of constructs I cannot touch on in the space available here.
I construct the program using two packages and a main procedure; I present the package interfaces and main procedure first and then examine the package bodies.
10.4.4.3. A Tasking-Safe Random Number Generator
The naps taken by the three subtasks are to be of random duration. The package HB.Random_Task provides a random number facility that is concurrency-safe, that is, it ensures that the random number generator cannot be simultaneously called by more than one subtask. This prevents the seed of the generator from being corrupted by a second call arriving in the midst of a computation. The interface of this package is shown here.
1 package HB.Random_Task is 2 3 subtype RandomRange is Natural range 1..10; 4 5 task Randomizer is 6 entry GiveNumber (Result: out RandomRange); 7 end Randomizer; 8 9 end HB.Random_Task;
Lines 3-5 give the interface to a task Randomizer. The task is Adas construct for a concurrent subtask. A task cannot be a compilation unit; it must be declared within a package or main unit. A task has an interface and an implementation (body); the task can provide services by means of entries, which are called by other tasks. An entry declaration is syntactically similar to a procedure declaration; an entry call is syntactically similar to a procedure call, but its behavior is different. Specifically, the Ada standard ensures that multiple calls to a given tasks entries are executed one at a time. Given a variable, in some other task,
R: HB.Random_Task.RandomRange;
the entry call, in that other task,
HB.Random_Task.Randomizer.GiveNumber(Result=>R);
deposits in R a random number in the range 1..10. Moreover, this call is guaranteed to complete before another is allowed to begin. You will see how this is done when I examine the body of HB.Random_Task.
10.4.4.5. A Tasking-Safe Screen Manager
Each of the subtasks must be able to write to its own window on the screen. The second package is HB.Screen. This package provides a tasking-safe manager for the terminal screen, which I assume has ANSI or VT100 screen-control characteristics.
1 package HB.Screen is 2 3 ScreenHeight : constant Integer := 24; 4 ScreenWidth : constant Integer := 80; 5 6 subtype Height is Integer range 1..ScreenHeight; 7 subtype Width is Integer range 1..ScreenWidth; 8 9 type Position is record 10 Row : Height := 1; 11 Column: Width := 1; 12 end record; 13 14 task Manager is 15 entry Beep; 16 entry ClearScreen; 17 entry Write (Item: in String; Where: in Position); 18 end Manager; 19 20 end HB.Screen;
The package exports a task Manager with entries providing Beep, ClearScreen, and Write services. Why is this task necessary?
To write a given string at a given row/column position requires two operations: positioning the cursor at the correct position and writing the string there. Using ANSI standard screen-control commands, positioning the cursor to row 15, column 35 requires me to send, to the terminal, the string
ESC [ 15 ; 35 f
where ESC is the escape character and the blanks are included for clarity here but are not sent. This is a total of eight characters; moreover, this control string must be immediately followed by the string to be displayed.
It is fundamental to concurrent programming that wherever multiple processes (threads, tasks) are running and writing to a shared resource (in this case, the standard output file), it is possible that one of the processes will be interrupted (preempted, swapped out) and one of its sibling processes will gain control. If this interruption happens to occur in the midst of a transaction to the screen, that is, the command followed by the string, the process gaining control might start another screen transaction. Because the terminal itself has no idea that this has occurred, it interprets the incoming character stream as best it can; this is likely to result in a chaotic mess on the screen because half-completed commands can be randomly intermixed with displayed strings. For example, here is the output from an execution of a similar program without this protection:
1;21fA nap 7 secs41fB nap 7 secsC nap 4 secs C nap 4 secs[2;1f21fA nap 4 secsB nap 7 A nap 1 secs B nap 8 secs C nap 1 secs A nap 4 secs B nap 9 secs C nap 6 secs A nap 8 secs B nap 7 secs B nap 5 secs B nap 2 secs
I cannot allow this chaos to occur, so I must provide mutual exclusion that allows at most one task at a time to send a screen transaction and ensures that this transaction is completed before another one is begun. Implementing the screen services as entries of a task provides the desired mutual exclusion; I return to the details after examining the main program.
A real program normally uses a window manager of some sort for this window-like display style. Window managers are, of course, generally provided by platform-dependent application programming interfaces (APIs); it is interesting to note that APIs are not necessarily concurrency-safe, and an API-using concurrent programin Ada or another languagemay well have to provide its own safety mechanisms. I recently developed some multitasking Ada programs using the Apple Macintosh Toolbox API (Feldman, 1997b); the window manager and event handler design required me to develop Ada-level safety mechanisms similar to the one here.
Previous | Table of Contents | Next |