In LispWorks 5.1 and previous versions, the main way to synchronize between threads is to use
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).
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.
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.
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.
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.
condition-variable-wait-count can be used to determine the current number of threads waiting for a condition variable.
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
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.
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 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:
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
See also barrier-block-and-wait.
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.
LispWorks User Guide and Reference Manual - 13 Feb 2015