Remember that in Ada, a task specification looks like an abstract type (ADT) specification. The outside world (i.e. any other task) interacts with a task by calling its "entries".
Note that "off by one" mistakes are easy in the code above. Ada helps in detecting these mistakes by allowing precise, explicit subranges for integer variables.task body buffer is
size: constant integer := 10;
b: array (1..size) of item;
n: integer range 0..size := 0;
inloc, outloc: integer range 1..size := 1;
begin
loop
select
when n < size =>
accept give (x: in item) do
b(inloc) := x;
end give;
n := n+1;
inloc := (inloc mod size) + 1;
or when n > 0 =>
accept take (x: out item) do
x := b(outloc);
end take;
n := n-1;
outloc := (outloc mod size) + 1;
end select;
end loop;
end buffer;
Synchronization means reaching the same point at the same time. One thread must do a "send" at the same time (or immediately before) the other does a "receive".
An Ada rendezvous is like a telephone call: during the rendezvous the caller and the callee are synchronized. The caller chooses the callee and knows which task this is. The callee does not know who the caller is.
A caller can be a hardware interrupt or a trap (i.e. an interrupt generated by low-level software).
Execution of the caller of an entry is suspended while the callee is executing code between the accept line and the corresponding end.
"Mutual exclusion" means that only one thread at a time can have access
to a resource. Concurrent programming always involves mutual exclusion.
Mutual exclusion can be guaranteed if a resource is only manipulated inside
accept blocks.
Note that in Ada pointers are dereferenced automatically when this makes sense from the syntactic context.task type printer is
entry init(id: string);
entry print(s: string);
end printer;task body printer is
...
begin
accept init(id: string) do
...
end init;
...
end printer;type printerptr is access printer;
p := new printer; p.init("123.456.000.000");
q := new printer;
In general, message-passing can be implemented using shared memory: first the sender sets the value of a variable to be the message, then the receiver uses the variable.
Conversely, shared memory can be simulated using message-passing, with
a third thread.
An operation is called atomic if it is guaranteed to be performed "all or nothing", i.e. with mutual exclusion.
Typically the most primitive concurrent programming operation, implemented in hardware, is "test and set". In pseudo-code this operation on a shared register X is
"Test and set" is implemented by the CPU in hardware to be atomic. It can be used to build more complicated atomic operations.if X == 0 then { X := 1; return 0 }
else return 1
See The
four ACID properties by Jim Gray.
Copyright (c) 1999 by Charles Elkan.