Chapter 17. Synchronization Extension

Table of Contents
17.1. lock statement
17.2. unlock statement
17.3. $LOCK classes
17.4. Attach statement
17.5. $ATTACH classes
17.6. sync statement
17.7. Memory consistency
17.8. SYS class

The synchronization extension allows threads to block; this requires threading facilities not available on every platform. Programmers should not assume that synchronization is less expensive than thread creation; creating threads as required may be more efficient than attempting to manage a pool of threads that wait for things to do. Generally, minimizing synchronization provides the greatest throughput.

17.1. lock statement

Example 17-1. Examples:

   when m then ...
   else ...
   guard d.size > 0 when m then ...
   when rw.writer then ...
lock_statement ==>
         lock expression { , expression } then statement_list [ else statement_list ] end lock lock_when { lock_when } [ else statement_list ] end
lock_when ==>
        [ guard expression ] when expression { , expression } then  statement_list

Locks are special built-in synchronization objects that control the blocking and unblocking of threads. A thread acquires a lock, then holds the lock until it releases it. A single thread may acquire a lock multiple times recursively; it will be held until a corresponding number of releases occur. Exclusive locks, such as 'MUTEX', may only be held by one thread at a time. In addition to these simple exclusive locks, it is possible to lock on other more complex synchronization types (on See $LOCK classes).

Locks may be safely acquired with the lock statement. Expressions following a 'when' or 'lock' are called locking conditions, and must be subtypes of $LOCK (See $LOCK classes). The statement list following the 'then' is called the lock branch. A lock statement guarantees that all listed locks are atomically acquired before a lock branch executes. Expressions following a 'guard' are called guarding conditions. The statements following the 'else' are called the else branch. The 'when' is dropped in the first form, convenient when there is only a single locking condition and no guard.

When a lock statement is entered the following occur in strict order:

  1. Any guarding conditions are evaluated in textual order. If any evaluate to 'false', the corresponding when clause will not be considered further. when clauses without a guarding condition or for which the condition evaluates to 'true' are accepted.

  2. If no when clauses are accepted, the else branch executes; it is a fatal error if there is no else clause in such a case.

  3. For all accepted clauses, all locking conditions are evaluated, in textual order, left to right.

  4. If the locking conditions of some when clause can be immediately satisfied, those locks are obtained, the corresponding lock branch executes, and execution concludes without considering other accepted when clauses.

  5. If there is an 'else' clause and no when clauses have lock conditions that can immediately be satisfied, then the else branch executes. If there is no 'else' clause, the executing thread blocks until the locking conditions of some when clause can be satisfied. After the locking conditions are locked atomically, the corresponding lock branch executes.

Because all listed locks are acquired atomically, deadlock can never occur due to concurrent execution of two or more lock statements with multiple locks, although it is possible for deadlock to occur by dynamic nesting of lock statements or through other synchronization.

The implementation of lock statements also ensures that threads that can run will eventually do so; no thread will face starvation because of the operation of the locking and scheduling implementation. Similarly, no when clause will be repeatedly chosen over another such that a clause starves. However, it is frequently good practice to have threads whose programmer supplied enabling conditions are never met in a given run (exceptional cases) or are not met after some time (alternative methods). One thread in an infinite loop can prevent other threads from executing for an arbitrary time, unless it calls SYS::defer (See SYS class).

All locks acquired by the lock statement are released when the lock or else branch stops executing; this may occur due to finishing the branch, termination of a loop by an iterator, a return, a quit, or an exception. yield may occur in a lock statement, but locks are not released until the iterator quits. Exceptions in a lock body will not be raised outside the body until all associated locks have been released.