![]() | ![]() | ![]() | 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.
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.
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.
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.
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. Note that 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.
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.
A scheduled function is an object that contains a function to be run at a specified time. When that specified time arrives, the function is invoked with a single argument: the scheduled function object. A repeat interval (in seconds) can also be specified for the scheduled function. This value is used whenever the scheduled function is invoked to schedule itself again at a new time relative to the current invocation. Scheduled functions can be scheduled to a resolution of one second.
Scheduled functions are scheduled and invoked by a separate
"Scheduled-Function Scheduler" thread. Unless the run time of
the invoked function is brief, the function should spawn a new
thread in which to perform its activities so as to avoid delaying the
invocation of a subsequent scheduled function.
A periodic function is a function to be run repeatedly at a
specified interval. Unlike scheduled functions, which can be
scheduled only to a resolution of one second, a periodic function can
be repeated at intervals as brief as is supported by the sleep function
of the Common Lisp implementation. A periodic function is scheduled
and executed in its own thread. As with scheduled functions,
however, function should spawn a new thread in which to
perform its activities, unless its run time is brief.
Entities
The GBBopen Project
![]() | ![]() | ![]() | Portable Threads | ![]() |