Threads may communicate by writing and then reading variables or attributes of objects. All assignments are atomic (the result of a read is guaranteed to be the value of some previous write); assignments to variables of immutable type atomically modify all attributes. Writes are always observed by the thread itself. Writes are not guaranteed to be observed by other threads until an export is executed by the writer and a subsequent import is executed by the reader, even if the writes were previously observed by the reading thread. Exports and imports may be written explicitly (See SYS class) and are also implicitly associated with certain operations:
|An import occurs:||An export occurs:|
|In a newly created thread||In parent thread when a child thread is forked|
|On exiting a par statement (children have terminated)||By a thread on termination|
|On entering one of the branches of a lock statement||On entering an unlock, or exiting a lock|
|On exiting exclusive operations (See $ATTACH classes)||On entering exclusive operations|
|On completion of a sync statement||On initiation of a sync statement|
This model has the property that it guarantees sequential consistency to programs without data races.
Example 17-10. This incorrect code may loop forever waiting for flag, print 'i is 1', or print 'i is 0'. The code fails because it is trying to use flag to signal completion of 'i:=1', but there is no appropriate synchronization occurring between the forked thread and the thread executing the par body. Even though the forked thread terminates, the modification of 'flag' may not be observed because there is no import in the body thread. Even if the modification to flag is observed, there is no guarantee that a modification to 'i' will be observed before this, if at all.
-- These variables are shared i: INT; flag: BOOL; par fork i := 1; flag := true; end; -- Attempt to loop until change -- in 'flag' is observed loop until!(flag); end; #OUT + 'i is' + i + '\n'; end;
Example 17-11. This code will always print 'i is 1' because there is no race condition (unlike the previous example). An export occurs when the forked thread terminates, and an import occurs when par completes. Therefore the change to 'i' must be observed.
i:INT; -- This is a shared variable par fork i:=1; end; end; #OUT + 'i is' + i + '\n';