Scieneer Common Lisp 1.3.9 online documentation

Thread Synchronisation

Introduction

The Scieneer Common Lisp supports the execution of multiple concurrent threads and this will often require synchronization of access to shared data structures and resources. A range of thread synchronization functions are provided including: atomic operations, memory barriers, and locks and condition variables.

Atomic operations

Atomic conditional setter primitives are provided which can be applied to a range of places including: symbol value cells, symbol property lists, cons car and cdr cells, simple-vector elements, structure slots except raw slots, and standard-object slots.

The kernel:setf-conditional macro atomically compares the value stored in a supported place to a provided old value and sets the place to a new value if the value in the place has not changed. This is a primitive operation supported by the processor machine code and executes quickly without blocking on a range of supported places. This macros may be used to build fast non-blocking thread synchronization operations.

A number of macros are provided for common atomic operations: the macro kernel:with-atomic-modification atomically modifiers a cell; kernel:atomic-push and kernel:atomic-pop atomically push and pop a list; kernel:atomic-incf and kernel:atomic-decf atomically increase or decrease a stored value; and kernel:with-atomic-getf-modification atomically modifies a property list.

Currently these macros fall back on thread:with-exclusive-execution if an unrecognized place is given. The exclusive execution context is fragile and its use degrades performance and scalability so a warning is generated, and in future an error may be generated if an atomic primitive is not available.

Atomic operations on structure-object and standard-object slots is implemented by the generic function kernel:set-slot-value-conditional allowing extension to new class by adding specialized methods. For example a databased backed object could implement this function naturally using an atomic database transaction.

The lower level conditional setter functions are also documented below and make be of utility in building thread synchronization functionality.

kernel:setf-conditional place old-value new-value &environment env[Macro]

Atomically compare the value in the specified place to old-value and if eq then store new-value in place. The original value in place is returned. The macro compiles to fast inline code for a range of places: special symbols, symbol-value, symbol-plist, car and cdr, svref, slot-value, and structure slot accessor functions. This macro may be used for fast inline thread synchronization, and forms the basis of a number of higher level macros: kernel:with-atomic-modification, kernel:atomic-push, kernel:atomic-pop, kernel:atomic-incf, and kernel:atomic-decf.

If the place is not yet supported for compilation to inline code then the operation is synchronized using thread:with-exclusive-execution. This is much slower and does not scale well on multi-processors systems because it stops all other threads, so a warning is generated.

kernel:with-atomic-modification (oldvar place) &parse-body (body decls) &environment env[Macro]

Atomically modify a given place with the result of the body. The oldvar is bound to the value in place and the body evaluated to generate a new value. Then an attempt is made to atomically update the place from oldvar to this new value, and if unsuccessful then loop re-binding oldvar and reevaluating the body. The new value is returned. See also: kernel:setf-conditional, and kernel:with-atomic-getf-modification.

kernel:atomic-push value place &environment env[Macro]

Thread safe push of value onto the list in the given place.

kernel:atomic-pop place &environment env[Macro]

Thread safe pop from the list in the given place.

kernel:atomic-incf place &optional delta[Macro]

Atomically increment place by delta.

kernel:atomic-decf place &optional delta[Macro]

Atomically decrease place by delta.

kernel:with-atomic-getf-modification (oldvar place indicator &optional default) &parse-body (body decls)[Macro]

Atomically modify the value associated with the property indicator in the property list at the given place. If the property does not exist then firstly attempt to atomically add the property with the default value. The oldvar is bound to current value of the property and the body evaluated to generate a new value. Then an attempt is made to atomically update the value cell from oldvar to this new value, and if unsuccessful then loop re-binding oldvar and reevaluating the body until successful. The new value is returned. See also: kernel:setf-conditional, and kernel:with-atomic-modification.

kernel:set-slot-value-conditional instance slot-name old new[Function]

Atomically compare the value of slot slot-name to the old value and if eq then set the slot value to the new value. The original value of the slot is returned. Implemented by clos:set-slot-value-conditional-using-class.

kernel:set-symbol-value-conditional symbol old new[Function]

Atomically compare symbol's value to old and if eq then store new in symbol's value cell. The original symbol value is returned. If the symbol has a binding in the current thread then the symbol dynamic value cell is accessed, otherwise the symbol global value cell is accessed. See also: kernel:set-symbol-value-conditional and kernel:set-symbol-global-value-conditional.

kernel:set-symbol-global-value-conditional symbol old new[Function]

Atomically compare symbol's global value to old and if eq then store new in symbol's global value cell. The original symbol value is returned.

kernel:set-symbol-dynamic-value-conditional symbol old new[Function]

Atomically compare symbol's dynamic value to old and if eq then store new in symbol's dynamic value cell. The original symbol value is returned.

kernel:set-symbol-plist-conditional symbol old new[Function]

Atomically compare symbol's property list to old and if eq then store new in symbol's property list cell. The original property list is returned.

kernel:rplaca-conditional cons old new[Function]

Atomically compare the car cell of cons to old and if eq then store new in the cell. The original cell value is returned.

kernel:rplacd-conditional cons old new[Function]

Atomically compare the cdr cell of cons to old and if eq then store new in the cell. The original cell value is returned.

kernel:data-vector-set-conditional vector index old new[Function]

Atomically compare an element of vector to old and if eq then store new in the element. The original cell value is returned.

kernel:define-structure-slot-conditional-setter struct slot &optional name[Macro]

Define a function to conditionally set a structure slot. The defined function name defaults to <structure>-<slot>-set-conditional.

kernel:%instance-set-conditional object slot-index old new[Function]

Atomically compare the structure object's slot at slot-index to old and if eq then store new in the slot. The original slot value is returned.


Memory barriers

Memory barriers are necessary to achieve a safe ordering of memory read and writes in a multi-processor environment.

kernel:barrier [Function]

Ensure that the compiler emits all pending instructions before the barrier.

kernel:memory-barrier [Function]

Ensure that all read and write operations that were issued before the barrier have completed before any read or write operations after the barrier.

kernel:read-memory-barrier [Function]

Ensure that all read operations that were issued before the barrier have completed before any read operations after the barrier.

kernel:write-memory-barrier [Function]

Ensure that all write operations that were issued before the barrier have completed before any write operations after the barrier.

kernel:acquire-memory-barrier [Function]

Ensure that all atomic read-write operations that were issued before the barrier have completed before any read or write operations after the barrier.

kernel:release-memory-barrier [Function]

Ensure that all read and write operations that were issued before the barrier have completed before any write or atomic read-write operations after the barrier.


Locks

Locks are build on the POSIX thread library lock functions.

thread:lock[Class]

Class precedence list:

thread:lock, structure-object, ext:instance, t.

thread:make-lock &optional name &key type auto-free interruptible[Function]

Make a pthread-mutex based lock, returning a lisp thread:lock object encapsulating the pthread-mutex structure. The lock type may be :normal, :recursive, or :error-check. When auto-free is t, the pthread-rwlock structure will be automatically freed when the lisp object is no longer accessible, and reallocated when lisp is restarted. When auto-free is :note, the lock is noted and reallocated upon restart, but is not automatically freed.

thread:destroy-lock lock[Function]

Destroy and free the pthread-mutex structure associated with a lock.

thread:with-lock-held (lock &optional whostate &key :wait :timeout) &body body[Macro]

Execute the body with the lock held. If the lock is held by another thread then wait until the lock is released or an optional :timeout is reached. The results of the body are returned upon success. When :wait is nil and the lock is held by another thread then nil is returned immediately without processing the body.

thread:read-write-lock[Class]

Class precedence list:

thread:read-write-lock, structure-object, ext:instance, t.

thread:make-read-write-lock &optional name &key kind auto-free interruptible[Function]

Make a pthread-rwlock based lock, returning a lisp thread:read-write-lock object encapsulating the pthread-rwlock structure. The lock :kind may be either :prefer-reader or :prefer-writer. When :auto-free is t, the pthread-rwlock structure will be automatically freed when the lisp object is no longer accessible, and reallocated when lisp is restarted. When :auto-free is :note, the lock is noted and reallocated upon restart, but is not be automatically freed.

thread:destroy-read-write-lock lock[Function]

Destroy and free the pthread-rwlock structure associated with the thread:read-write-lock class lock.

thread:with-read-lock-held (lock &optional whostate &key :wait) &body body[Macro]

Execute the body with the thread:read-write-lock class lock held for reading, returning the results of the body upon success. If the lock is already held for writing then by default wait until it is released, otherwise if the wait key is nil then immediately return nil without processing the body.

thread:with-write-lock-held (lock &optional whostate &key :wait) &body body[Macro]

Execute the body with the thread:read-write-lock class lock held exclusively for writing, returning the results of the body upon success. If the lock is already held for either reading or writing then by default wait until it is released, otherwise if the wait key is nil then immediately return nil without processing the body.


Condition variables

Condition variables are build on the POSIX thread library functions.

thread:cond-var[Class]

Class precedence list:

thread:cond-var, structure-object, ext:instance, t.

thread:make-cond-var &optional name &key auto-free[Function]

Make a new condition variable encapsulating a pthread condition variable. When auto-free is t, the pthread structure will be automatically freed when the lisp object is no longer accessible, and reallocated when lisp is restarted. When auto-free is :note, the condition variable is noted and reallocated upon restart but is not automatically freed.

thread:destroy-cond-var cond-var[Function]

Destroy and free the associated pthread condition variable structure.

thread:cond-var-signal cond-var[Function]

Restart one of the threads waiting on the condition variable.

thread:cond-var-broadcast cond-var[Function]

Restart all threads waiting on the condition variable.

thread:cond-var-wait cond-var lock &optional whostate[Function]

Unlock the lock and wait for the condition variable to be signalled. The lock must be locked before calling and is reacquired upon return.

Note that on some ports, such as Redhat Linux 9.0, a lisp interrupt will not interrupt this wait so the interrupt will not be handled until the condition variable is signaled and the call returns to lisp. It may be more appropriate to use thread:cond-var-timedwait for which lisp interrupts will be handled upon a timeout and return to lisp.

thread:cond-var-timedwait cond-var lock timeout &optional whostate[Function]

Unlock the lock and wait for either the condition variable to be signalled returning true, or timeout returning nil. The lock must be locked before calling and is reacquired before return.