An Ada task is an interesting structure. It has aspects of a package, a procedure, and a data structure but is really none of these; it is something different altogether:
Program 16.1
A Task within a Main Program
WITH Ada.Text_IO; PROCEDURE One_Task IS ------------------------------------------------------------------------ --| Show the declaration of a simple task type and one --| variable of that type. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: December 1995 ------------------------------------------------------------------------ -- A task type has a specification TASK TYPE SimpleTask (Message: Character); -- A task type has a body TASK BODY SimpleTask IS BEGIN -- SimpleTask FOR Count IN 1..10 LOOP Ada.Text_IO.Put(Item => "Hello from Task " & Message); Ada.Text_IO.New_Line; END LOOP; END SimpleTask; Task_A: SimpleTask(Message => 'A'); BEGIN -- One_Task -- Unlike procedures, tasks are not "called" but are activated -- automatically. -- Task_A will start executing as soon as control reaches this -- point, just after the BEGIN but before any of the main program's -- statements are executed. NULL; END One_Task;Sample Run
Hello from Task A Hello from Task A Hello from Task A Hello from Task A Hello from Task A Hello from Task A Hello from Task A Hello from Task A Hello from Task A Hello from Task A
First
note the overall structure of the program. A task type,
SimpleTask
, is declared with a discriminant, Message
.
This task specification is followed by a task body in which the message is
displayed ten times. Next, Task_A
is declared as a task variable,
usually called a task object, with a discriminant value of 'A'
.
Reaching the main BEGIN
of this program, we discover that the
program has no executable statements, just a NULL
statement to
satisfy the rule that a procedure must have at least one statement. Yet the
sample run shows the task actually displaying Hello from Task A
ten times. The task was never called from the main program, yet it executed
anyway.
In fact, the task began its execution just after the main BEGIN
was reached. In Ada, this is called "task activation": All tasks declared in a
given block are activated just after the BEGIN
of that block. Here
there is only one task, Task_A
.
Multiple Task Objects of the Same Type
Program
16.2 shows the declaration of two task objects, Task_A
and
Task_B
. Further, the task type is modified to allow two
discriminants, the message and the number of times the message is to be
displayed. A discriminant acts here like a parameter of the task, but it is not
a fully general parameter; like a variant-record discriminant, it must be of a
discrete--integer or enumeration--type. A string, for example, cannot be used
as a task discriminant.
Program 16.2
Two Tasks within a Main Program
WITH Ada.Text_IO; PROCEDURE Two_Tasks IS ------------------------------------------------------------------------ --| Show the declaration of a simple task type and two --| variables of that type. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: December 1995 ------------------------------------------------------------------------ -- A task type has a specification TASK TYPE SimpleTask (Message: Character; HowMany: Positive); -- A task type has a body TASK BODY SimpleTask IS BEGIN -- SimpleTask FOR Count IN 1..HowMany LOOP Ada.Text_IO.Put(Item => "Hello from Task " & Message); Ada.Text_IO.New_Line; END LOOP; END SimpleTask; -- Now we declare two variables of the type Task_A: SimpleTask(Message => 'A', HowMany => 5); Task_B: SimpleTask(Message => 'B', HowMany => 7); BEGIN -- Two_Tasks -- Task_A and Task_B will both start executing as soon as control -- reaches this point, again before any of the main program's -- statements are executed. The Ada standard does not specify -- which task will start first. NULL; END Two_Tasks;Sample Run
Hello from Task B Hello from Task B Hello from Task B Hello from Task B Hello from Task B Hello from Task B Hello from Task B Hello from Task A Hello from Task A Hello from Task A Hello from Task A Hello from Task AAs in Program 16.1,
Task_A
and Task_B
are activated just after
the main BEGIN
. Now there are two tasks; in which order are they
activated? The Ada standard does not specify this, leaving it instead up to the
compiler implementer. In a short while, we shall see how to control the order
in which tasks start their work.
Looking at the sample run from
Program
16.2, we see that Task_B
evidently started--and completed--its
work before Task_A
even started its own work. This tells us first
that the compiler we used activated Task_B
first, and also that,
once scheduled for the CPU, Task_B
was allowed to continue
executing until it completed its run. This seems odd: The tasks do not really
execute as though they were running in parallel; there is, apparently, no
time-sharing. If there were, we would expect Task_A
and
Task_B
output to be interleaved in some fashion.
In fact, the Ada standard allows, but does not require, time-slicing.
Time-slicing, implemented in the run-time support software, supports
"parallel" execution by giving each task a slice, usually called a
quantum, which is a certain amount of time on the CPU. At the end of the
quantum, the run-time system steps in and gives the CPU to another task,
allowing it a quantum, and so on, in "round-robin" fashion.
Cooperating Tasks
If
Program
16.2 were compiled for a computer with several processors, in theory
Task_A
and Task_B
could have been executed--truly in
parallel--on separate CPUs, and no time-slicing would be needed. With a single
CPU, we'd like to emulate the parallel operation, ensuring concurrent execution
of a set of tasks even if the Ada run-time system does not time-slice.
To get "parallel" behavior portably, using one CPU or many, with or without time-slicing, we code the tasks in a style called cooperative multitasking; that is, we design each task so that it periodically "goes to sleep," giving up its turn on the CPU so that another task can execute for a while.
Program
16.3 shows how this is done, using a DELAY
statement in each
iteration of the task body's FOR
loop. The DELAY
causes the task to suspend its execution, or "sleep." Now while
Task_A
is "sleeping," Task_B
can be executing, and so
on. The cooperating nature of the two tasks is easily seen in the sample output.
Program
16.3
Using DELAY
to Achieve Cooperation
WITH Ada.Text_IO; PROCEDURE Two_Cooperating_Tasks IS ------------------------------------------------------------------------ --| Show the declaration of a simple task type and two --| variables of that type. The tasks use DELAYs to cooperate. --| The DELAY causes another task to get a turn in the CPU. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: December 1995 ------------------------------------------------------------------------ -- A task type has a specification TASK TYPE SimpleTask (Message: Character; HowMany: Positive); -- A task type has a body TASK BODY SimpleTask IS BEGIN -- SimpleTask FOR Count IN 1..HowMany LOOP Ada.Text_IO.Put(Item => "Hello from Task " & Message); Ada.Text_IO.New_Line; DELAY 0.1; -- lets another task have the CPU END LOOP; END SimpleTask; -- Now we declare two variables of the type Task_A: SimpleTask(Message => 'A', HowMany => 5); Task_B: SimpleTask(Message => 'B', HowMany => 7); BEGIN -- Two_Cooperating_Tasks -- Task_A and Task_B will both start executing as soon as control -- reaches this point, again before any of the main program's -- statements are executed. The Ada standard does not specify -- which task will start first. NULL; END Two_Cooperating_Tasks;Sample Run
Hello from Task B Hello from Task A Hello from Task B Hello from Task A Hello from Task B Hello from Task A Hello from Task B Hello from Task A Hello from Task B Hello from Task A Hello from Task B Hello from Task B
We know that the Ada standard does not specify an order of activation for multiple tasks in the same program. Each compiler can use a different order; indeed, a compiler is--theoretically--free to use a different starting order each time the program is run, though practical compilers rarely if ever take advantage of this freedom.
Although we cannot control the actual activation order of tasks, we can gain control of the order in which these tasks start to do their work by using a so-called "start button." This is a special case of a task entry, which is a point in a task at which it can synchronize with other tasks. This is illustrated in Program 16.4.
Program 16.4
WITH Ada.Text_IO; PROCEDURE Start_Buttons IS ------------------------------------------------------------------------ --| Show the declaration of a simple task type and three --| variables of that type. The tasks use DELAYs to cooperate. --| "Start button" entries are used to to control starting order. --| Author: Michael B. Feldman, The George Washington University --| Last Modified: December 1995 ------------------------------------------------------------------------ TASK TYPE SimpleTask (Message: Character; HowMany: Positive) IS -- This specification provides a "start button" entry. ENTRY StartRunning; END SimpleTask; TASK BODY SimpleTask IS BEGIN -- SimpleTask -- The task will "block" at the ACCEPT, waiting for the "button" -- to be "pushed" (called from another task, Main in this case). ACCEPT StartRunning; FOR Count IN 1..HowMany LOOP Ada.Text_IO.Put(Item => "Hello from Task " & Message); Ada.Text_IO.New_Line; DELAY 0.1; -- lets another task have the CPU END LOOP; END SimpleTask; -- Now we declare three variables of the type Task_A: SimpleTask(Message => 'A', HowMany => 5); Task_B: SimpleTask(Message => 'B', HowMany => 7); Task_C: SimpleTask(Message => 'C', HowMany => 4); BEGIN -- Start_Buttons -- Tasks will all start executing as soon as control -- reaches this point, but each will block on its ACCEPT -- until the entry is called. In this way we control the starting -- order of the tasks. Task_B.StartRunning; Task_A.StartRunning; Task_C.StartRunning; END Start_Buttons;Sample Run
Hello from Task B Hello from Task A Hello from Task C Hello from Task B Hello from Task A Hello from Task C Hello from Task B Hello from Task A Hello from Task C Hello from Task B Hello from Task A Hello from Task C Hello from Task B Hello from Task A Hello from Task B Hello from Task B
In this program, the task specification is expanded to include an entry specification:
ENTRY StartRunning;
This is, syntactically, similar to the subprogram
specifications that usually appear in package specifications. The task body
includes, immediately after its BEGIN
, the corresponding line
ACCEPT StartRunning;
According to the rules of Ada, a SimpleTask
object, upon reaching an ACCEPT
statement, must wait at
that statement until the corresponding entry is called by another task. In
Program 16.4, then, each task--Task_A
, Task_B
, and
Task_C
--is activated just after the main program's
BEGIN
, but--before it starts any work--each reaches its respective
ACCEPT
and must wait there (in this simple case, possibly forever)
until the entry is called.
How is the entry called? In our first three examples, the main program had nothing to do. In this case, its job is to "press the start buttons" of the three tasks, with the entry call statements
Task_B.StartRunning; Task_A.StartRunning; Task_C.StartRunning;
These statements are syntactically similar to procedure calls.
The first statement "presses the start button" of Task_B
. Since
Task_B
was waiting for the button to be pressed, it accepts the
call and proceeds with its work.
The main program is apparently executing--in this case, pressing the start
buttons--in "parallel" with the three tasks. In fact, this is true. In a
program with multiple tasks, the Ada run-time system treats the main program as
a task as well.
SYNTAX DISPLAY
Task Type Specification
TASK TYPE tname ( optional list of discrimnents ) IS ENTRY e1; ENTRY e2; . . . END tname;
TASK TYPE Philosopher (Name: Natural) IS ENTRY Come_to_Life (First, Second: Chopstick); END Philosopher;
SYNTAX DISPLAY
Task Body
TASK BODY tname IS local declaration-section BEGIN statement sequence END tname;
SimpleTask
bodies shown in this section serve as examples;
we need not repeat them here.
A task body can contain code that is much more interesting than we have
seen. Ada provides the SELECT
statement to give a programmer much
flexibility in coding task bodies. For example, using the
SELECT
,
ACCEPT
statement can be written to "time out" if a call
is not received within a given time interval.
The SELECT
construct is one of the most interesting in all of
programming; entire books can be written about the possibilities it offers.
Space does not permit a full discussion of the SELECT
here; we
hope this brief discussion has sparked your curiosity to learn more about it.
In this section, we have seen the basics of task types and objects. We now proceed to introduce protected types and objects.
Copyright © 1996 by Addison-Wesley Publishing Company, Inc.