Additional GBBopen ToolsScheduled and Periodic FunctionsPortable ThreadsGoTo Top

Portable Threads

GBBopen's Portable Threads provides a uniform interface to commonly used thread (multiprocessing) entities. Wherever possible, these entities do something reasonable in Common Lisp implementations that do not provide threads. However, entities that make no sense without threads signal errors in non-threaded implementations (as noted with each entity). The feature :threads-not-available is added on Common Lisp implementations without thread support, and the feature :with-timeout-not-available is added on implementations that do not support with-timeout.

Portable Threads entities are provided by the :portable-threads module in GBBopen. Stand-alone use of the Portable Threads interface is also easy, requiring only the portable-threads.lisp file for the portable-interface layer and, if desired, scheduled-periodic-functions.lisp for the scheduled and periodic function entities.

Threads and Processes

Common Lisp implementations that provide multiprocessing capabilities use one of two approaches:

There are advantages and complexities associated with each approach, and the Portable Threads Interface is designed to provide a uniform abstraction over them that can be used to code applications that perform consistently and efficiently on any supported Common Lisp implementation.

Locks

Common Lisp implementations provide differing semantics for the behavior of mutual-exclusion locks that are acquired recursively by the same thread: some always allow recursive use, others provide special “recursive” lock objects in addition to non-recursive locks, and still others allow recursive use to be specified at the time that a lock is being acquired. To enable behavioral consistency in all Common Lisp implementations, the :portable-threads interface module provides (non-recursive) locks and recursive locks and a single acquisition form, with-lock-held, that behaves appropriately for each lock type.

Condition Variables

POSIX-style condition variables provide an atomic means for a thread to release a lock that it holds and go to sleep until it is awakened by another thread. Once awakened, the lock that it was holding is reacquired atomically before the thread is allowed to do anything else.

A condition variable must always be associated with a lock (or recursive lock) in order to avoid a race condition created when one thread signals a condition while another thread is preparing to wait on it. In this situation, the second thread would be perpetually waiting for the signal that has already been sent. In the POSIX model, there is no explicit link between the lock used to control access to the condition variable and the condition variable. The Portable Threads Interface makes this association explicit by bundling the lock with the condition-variable CLOS object instance and allowing the condition-variable object to be used directly in lock entities.

Hibernation

Sometimes it is desirable to put a thread to sleep (perhaps for a long time) until some event has occurred. The Portable Threads Interface provides two entities that make this situation easy to code: hibernate-thread and awaken-thread. Thread hibernation can only be performed by the thread on itself, eliminating issues of a thread being hibernated at an undesirable time. Note that there is the potential for a hibernate/awaken race condition if a thread hibernates itself again soon after being awakened (when a second awaken-thread intended for the original hiberation is applied to the second hibernation rather than being ignored because the target thread is not hibernating). Using a condition-variable is preferable in this situation.

When a thread is hibernating, it remains available to respond to run-in-thread and symbol-value-in-thread operations as well as to be awakened by a dynamically surrounding with-timeout.

What about Process Wait?

Thread coordination functions, such as process-wait, are expensive to implement with operating-system threads. Such functions stop the executing thread until a Common Lisp predicate function returns a true value. With application-level threads, the Lisp-based scheduler evaluates the predicate function periodically when looking for other threads that can be run. With operating-system threads, however, thread scheduling is performed by the operating system and evaluating a Common Lisp predicate function requires complex and expensive interaction between the operating-system thread scheduling and the Common Lisp implementation. Given this cost and complexity, many Common Lisp implementations that use operating-system threads have elected not to provide process-wait-style coordination functions, and this issue extends to the Portable Threads Interface as well.

Fortunately, most uses of process-wait can be replaced by a different strategy that relies on the producer of a change that would affect the process-wait predicate function to signal the event rather than having the consumers of the change use predicate functions to poll for it. Condition variables, the Portable Threads hibernate-thread and awaken-thread mechanism, or blocking I/O functions cover most of the typical uses of process-wait.

Entities


The GBBopen Project


Additional GBBopen ToolsScheduled and Periodic FunctionsPortable ThreadsGoTo Top