Access to all Common Lisp objects is thread-safe in the sense that it does not cause an error because of threading issues.
Immutable (or read-only) objects such as numbers, characters, functions, pathnames and restarts can be freely shared between threads.
This section outlines for which types of mutable Common Lisp object access is atomic. That is, each value read from the object will correspond to the state at some point in time. Note however, that if several values are read, there is no guarantee about how these values will relate to each other if they are being modified by another thread (see Issues with order of memory accesses).
When one of these mutable atomic objects is modified, readers see either the old or new value (not something else), and it is guaranteed that the Lisp image is not corrupted by the modification even if multiple threads read or write the object simultaneously.
Access to conses, simple arrays except arrays with element type of integer with less than 8 bits, symbols, packages and structures is atomic. Note that this does not apply to non-simple arrays.
Slot access in objects of type standard-object
is atomic with respect to modification of the slots and with respect to class redefinition.
vector-pop
, vector-push
, vector-push-extend
, (setf fill-pointer)
and adjust-array
are all atomic with respect to each other, and with respect to other access to the array elements.
The Common Lisp functions that access hash tables are atomic with respect to each other. See also modify-hash for atomic reading and writing an entry and with-hash-table-locked. See also Modifying a hash table with multiprocessing for thread-safe ways to ensure a table entry.
Note that pathnames cannot be modified, and therefore access to them is always atomic.
Access to synchronization objects (mailboxes, barriers, semaphores and condition variables) is atomic. More information about these objects is in Synchronization between threads.
Operations on editor buffers (including points) are atomic and thread-safe as long as their arguments are valid. This includes modification to the text. However, buffers and points may become invalid because of execution on another thread. The macros editor:with-buffer-locked
and editor:with-point-locked
should be used around editor operations on buffers and points that may be affected by other processes. Note that this is applicable also to operations that do not actually modify the text, because they can behave inconsistently if the buffer they are looking at changes during the operation. See the
LispWorks Editor User Guide
for details of these macros.
This section outlines for which types of mutable Common Lisp object access is not atomic.
Access to arrays with element type of integer of less than 8 bits is not guaranteed to be atomic.
Access to non-simple arrays is not guaranteed to be atomic.
Access to lists (including alists and plists) is not atomic. Lists are made of multiple cons objects, so although access to the individual conses is atomic, the same does not hold for the list as a whole.
Sequence operations which modify multiple elements are not atomic.
Macros that expand to multiple accesses are in general not atomic. In particular, modifying macros like push
and incf
are not atomic (but see the atomic versions of some of them in Low level atomic operations).
Making several calls to Common Lisp functions that access hash tables will not be atomic overall. However LispWorks provides thread-safe ways to ensure a hash table entry - see Modifying a hash table with multiprocessing. See also modify-hash for atomic reading and writing an entry and with-hash-table-locked.
Stream operations are in general not atomic. There is an undocumented interface for locking of streams when this is required - contact Lisp Support if you need this.
Operations on CAPI objects are not atomic in general. The same is true for anything in the LispWorks IDE. These operations need to be invoked from the thread that owns the object, for example by capi:execute-with-interface
or capi:apply-in-pane-process
.
When multiple threads access the same memory location, the order of those accesses is not generally guaranteed. You should therefore not attempt to implement "lockless algorithms" which depend on the order of memory accesses unless you have a good understanding of multiprocessing issues at the CPU level (see Ensuring order of memory between operations in different threads).
However, all of the Low level atomic operations and locking operations (see Locks) do ensure that all memory accesses that happen before them have finished and that all memory accesses that happen after them start after them. Therefore, normally there is nothing special to consider when using those operations. The modification check macros described in Aids for implementing modification checks also take care of this.
Access to hash tables and non-simple arrays can be improved where they are known to be accessed in a single thread context. That is, only one thread at the same time accesses them.
The make-hash-table
argument single-thread tells make-hash-table
that the table is going to be used only in single thread context, and therefore does not need to be thread-safe. Such a table allows faster access.
Similarly the make-array
argument single-thread creates an array that is single threaded. Currently, the main effect of single-thread is on the speed of vector-pop
, vector-push
. and vector-push-extend
on non-simple vectors. These operations are much faster on "single threaded" vectors, typically more than twice as fast as "multithreaded" vectors.
You can also make an array be "single-threaded" with set-array-single-thread-p.
The result of parallel access to a "single-threaded" vector is unpredictable.
LispWorks User Guide and Reference Manual - 13 Feb 2015