17.7. Memory consistency

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:

Table 17-5.

An import occurs:An export occurs:
In a newly created threadIn 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 statementOn entering an unlock, or exiting a lock
On exiting exclusive operations (See $ATTACH classes)On entering exclusive operations
On completion of a sync statementOn initiation of a sync statement

This model has the property that it guarantees sequential consistency to programs without data races.

17.7.1. Memory consistency examples

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';