All Manuals > LispWorks User Guide and Reference Manual > 19 Multiprocessing


19.7 Synchronization between threads

In LispWorks 5.1 and previous versions, the main way to synchronize between threads is to use mp:process-wait or mp:process-wait-with-timeout to supply a predicate to the scheduler. The predicate runs periodically in the background to identify threads that are no longer blocked.

These functions are still available, but there are some alternatives that can be more efficient in many cases by removing the need for the scheduler. The alternatives are:

Access to all of these objects is atomic and does not require additional locks (except for the lock that is already used with a condition variable).

19.7.1 Condition variables

A condition variable allows you to wait for some condition to be satisfied, based on the values stored in shared data that is protected by a lock. The condition is typically something like data becoming available in a queue.

The function condition-variable-wait is used to wait for a condition variable to be signaled. It is always called with the lock held, which is automatically released while waiting and reclaimed before continuing. More than one thread can wait for a particular condition variable, so after being notified about the condition changing, you should check the shared data to see if it represents a useful state and call condition-variable-wait again if not.

The function condition-variable-signal is used to wake exactly one thread that is waiting for the condition variable. If no threads are waiting, then nothing happens.

Alternatively, the function condition-variable-broadcast can be used to wake all of the threads that are waiting at the time it is called.

Any threads that wait after the call to condition-variable-signal or condition-variable-broadcast will not be woken until the next call.

In most uses of condition variables, the call to condition-variable-signal or condition-variable-broadcast should be made while holding the lock that waiter used when calling condition-variable-wait for this condition variable. This ensures that the signal is not lost if another thread is just about to call condition-variable-wait.

The function condition-variable-wait-count can be used to determine the current number of threads waiting for a condition variable.

The condition variable implementation in LispWorks aims to comply with the POSIX standard where possible.

condition-variable-wait, condition-variable-signal and condition-variable-broadcast have corresponding functions lock-and-condition-variable-wait, lock-and-condition-variable-signal and lock-and-condition-variable-broadcast. For condition-variable-wait there is also simple-lock-and-condition-variable-wait, which is simpler to use. The lock-and-condition-* functions perform the equivalent of locking and in the scope of the lock doing something and calling the corresponding condition-* function.

The lock-and-condition-* functions not only make it simpler to code, they also make it easier to avoid mistakes, and can optimize some cases (in particular, the quite common case when there is no need to lock on exit from condition-variable-wait). They are the recommended interface.

The lock-and-condition-* functions can be used together with condition-* functions on the same locks and condition variables.

Note: In cases when only one process waits for the condition, using process-wait-local for waiting and process-poke for signaling is easier, and involves less overhead.

19.7.2 Synchronization barriers

Barriers are objects that are used to synchronize multiple threads. A barrier has a count that determines how many "arrivals" (calls to barrier-wait) have to occur before these calls return.

The main usage of barriers is to ensure that a group of threads have all finished some stage of an algorithm before any of them proceeds.

The typical way of using a barrier is to make one with a count that is the same as the number of threads that are going to work in parallel and then create the threads to do the work. When each thread has done its work, it synchronizes with the others by calling barrier-wait. In most cases barrier-wait is the only barrier API that is used.

For example, assume you have a task that be broken into two stages, where each stage can be done in parallel by several threads, but the first stage must be completely finished before any processing of the second stage can start. Then the code will do:

(let ((barrier (mp:make-barrier num-of-processes)))
  (dotimes (p num-of-processes)
    (mp:process-run-function (format nil "Task worker ~d" p)
                             #'(lambda (process-number barrier)
                                 (do-first-stage process-number)
                                 (mp:barrier-wait barrier)
                                 (do-second-stage process-number))

It is also possible to use the barrier to block an indefinite number (up to most-positive-fixnum) of processes, until another process decides that they can go. For this the barrier is made with count t (or most-positive-fixnum). The other process then uses barrier-disable to "open" the barrier. If required, the barrier can be enabled again by barrier-enable.

See also barrier-block-and-wait.

19.7.3 Counting semaphores

A counting semaphore is a synchronization object that allows different threads to coordinate their use of a shared resource that contains some number of available units. The meaning of each unit depends on what the semaphore is being used to synchronize.

The three main functions associated with semaphores are: make-semaphore, which makes a new semaphore object; semaphore-acquire, which acquires units from a semaphore and semaphore-release, which releases units back to a semaphore. The current thread will block if it attempts to acquire more units than are current available.

The functions semaphore-name, semaphore-count and semaphore-wait-count can be used to query the name, available unit count and count of waiting units from a semaphore.

LispWorks User Guide and Reference Manual - 13 Feb 2015