--with-threads.CLISP uses the OS threads to implement multiple threads of execution. Two flavors are supported: POSIX and Win32. Both are preemptive.
All symbols are exported from the package “THREADS”, which has nicknames
“MT” (for MultiThreading) and
“MP” (for MultiProcessing).
When this functionality is present, *FEATURES*
contains the symbol :MT.
See also Section 35.8, “Garbage Collection and Multithreading”.
A program developed for a single-threaded world which shares no application objects with programs running in other threads must run fine, without problems.
Specfically: if, in a single-threaded world, execution of program A before program B produces semantically the same results as execution of program B before program A, then in a multithreaded world, it is possible to run A and B simultaneously in different threads, and the result will be the same as in the two single-threaded cases (A before B, or B before A).
Summary
Every dynamic variable has a global value that can be shared across all MT:THREADs.
Bindings of dynamic variables (via LET/LET*/MULTIPLE-VALUE-BIND)
are local to MT:THREADs, i.e. every SYMBOL has a different value cell in
each MT:THREAD. MT:SYMBOL-VALUE-THREAD can be used to inspect and modify
these thread local bindings.
Threads do not inherit dynamic bindings from the parent thread.
Example:
(defvar *global* 1) ; create a Global Variable (defun thread-1 () ; here *global* and (SYMBOL-VALUE*global*) will be 1 not 2! (setq *global* 5) ; change the Global Variable value (let ((*global* 10)) ; Per-Thread Variable value is initialized (setq *global* 20) ; Per-Thread Variable value is changed ; Global Variable value is not accessible here (only viaMT:SYMBOL-VALUE-THREAD) ) (setq *global* 30)) ; Global Variable value is modified again (let ((*global* 2)) ; Per-Thread Variable value is initialized (MT:MAKE-THREAD#'thread-1))
Locking discussed in this section has nothing to do with
EXT:PACKAGE-LOCK.
PACKAGE objects have an internal MT:MUTEX and are locked by
INTERN before adding a symbol (if FIND-SYMBOL fails). All
modifications of internal package data are guarded by this MT:MUTEX.
While iterating over package symbols with DO-SYMBOLS,
DO-EXTERNAL-SYMBOLS, DO-ALL-SYMBOLS, WITH-PACKAGE-ITERATOR
or the LOOP for-as-package
subclause the package being iterated over is also locked.
CLOS is not thread-safe. DEFCLASS, DEFGENERIC,
DEFMETHOD, DEFSTRUCT modify CLOS without any locking and may
interfere with each other.
It is recommended that all code is LOADed before
any MT:THREADs are spawned.
Nothing is ever locked automatically (automatic locking will
impose an unjustifiable penalty on HASH-TABLEs and SEQUENCEs
local to threads), so the user must use locks when sharing
HASH-TABLEs, SEQUENCEs and user-defined mutable objects between
threads.
This approach is consistent with the usual Common Lisp approach:
The consequences are undefined when code executed during an object-traversing operation destructively modifies the object in a way that might affect the ongoing traversal operation... | ||
| --[sec_3-6] | ||
If an object O1 is used as a key in a hash table H and is then visibly modified with regard to the equivalence test of H, then the consequences are unspecified if O1 is used as a key in further operations on H... | ||
| --[sec_18-1-2] | ||
RANDOM and RANDOM-STATERANDOM modifies a RANDOM-STATE without locking, which
means that you cannot carelessly share such objects between threads.
However, *RANDOM-STATE* is bound per-thread (see MT:MAKE-THREAD and
MT:*DEFAULT-SPECIAL-BINDINGS*), i.e., each thread has its own value and
thus RANDOM is thread-safe.
Here are some forms whose results are undefined if two threads evaluate them without locking:
(INCF(GETHASHx global-ht 0)) ; see Section 32.5.2.5, “Hash Tables, Sequences, and other mutable objects” (SETF(AREFglobal-array ...) ...) ; ditto (DEFMETHODgeneric-function(...) ...) ; see Section 32.5.2.4, “CLOS”
The above code may result in a segfault!
This is the reason why the multithreading support is still
experimental. However, if you do not share global HASH-TABLEs
between your threads and define all your CLOS methods before
spawning threads, multithreading should work fine.
The type of the object returned by MT:MAKE-THREAD.
Each MT:THREAD represent a separate computation, executed in parallel to each other.
(MT:MAKE-THREAD function
&KEY :NAME :INITIAL-BINDINGS :CSTACK-SIZE :VSTACK-SIZE)Start a new named MT:THREAD running function.
:INITIAL-BINDINGSan association list of (.
The symbol
. form)forms are EVALuated in the context of the new thread
and symbols are bound to the result in the thread before function is
called. The default value is MT:*DEFAULT-SPECIAL-BINDINGS*.
The main purpose of this argument is to initialize some
global data that should not be shared between threads, e.g.,
*RANDOM-STATE*, *READTABLE*.
When using :INITIAL-BINDINGS it is
best to CONS the application-specific data in front of
MT:*DEFAULT-SPECIAL-BINDINGS* or copy and modify it to fit the
application needs.
When the same symbol appears in this association list multiple
times, the first occurrence determines the value.
:CSTACK-SIZE:VSTACK-SIZESTACK in objects.
The default value is calculated based on the -m option
used when CLISP was started.
If 0, the value will be the same as that of the calling thread.
If MT:THREAD creation fails (e.g., due to a lack of system
memory), a CONTROL-ERROR is SIGNALed.
Cf. pthread_create.
(MT:THREADP object)(MT:THREAD-YIELD thread)Relinquish the CPU. The thread is placed at the end
of the run queue and another thread is scheduled to run.
Cf. sched_yield.
(MT:THREAD-INTERRUPT thread
&KEY :FUNCTION :OVERRIDE :ARGUMENTS)Interrupt the normal execution flow in thread and ask
it to APPLY function to arguments.
Use (
to debug MT:THREAD-INTERRUPT thread :FUNCTION NIL)thread and ( to terminate MT:THREAD-INTERRUPT thread :FUNCTION
T)thread.
The :OVERRIDE argument overrides MT:WITH-DEFERRED-INTERRUPTS
and should be used with extreme care.
Threads can only be interrupted at a point where the garbage-collection can run, see Section 35.8, “Garbage Collection and Multithreading”.
Currently on Win32 blocking I/O cannot be interrupted. The interrupt will be handled after the call returns.
Thread may be interrupted inside UNWIND-PROTECT's
cleanup forms and on non-local exit from function -
they may not execute entirely. In order to prevent this,
MT:WITH-DEFERRED-INTERRUPTS is provided.
(MT:THREAD-NAME thread)name of the thread.
(MT:THREAD-ACTIVE-P
thread)Return NIL if the thread has already terminated
and T otherwise.
By the time this function returns T, thread may have
already terminated anyway.
(MT:CURRENT-THREAD)(MT:LIST-THREADS)Return the LIST of all currently running MT:THREADs.
By the time this function returns, the set of
actually running threads may have a single intersection with the
return value - the MT:CURRENT-THREAD.
The type of the object return by MT:MAKE-MUTEX.
This represents a lock, i.e., a way to prevent different threads from doing something at the same time, e.g., modifying the same object.
(MT:MUTEXP object)(MT:MAKE-MUTEX &KEY
:NAME :RECURSIVE-P)Create new MT:MUTEX object - not locked by any thread.
:NAME should be a STRING describing the mutex (this really helps
debugging deadlocks). When RECURSIVE-P is
non-NIL, a recursive MT:MUTEX is created i.e., a thread can acquire
the mutex repeatedly (and should, of course, release it for each
successful acquisition).
Cf. pthread_mutex_init.
(MT:MUTEX-NAME thread)name of the MT:MUTEX.
(MT:MUTEX-LOCK mutex
&KEY :TIMEOUT)Acquire the mutex. If mutex is locked by
another thread, the call blocks and waits up to :TIMEOUT seconds.
If :TIMEOUT is not specified, waits forever.
Return T on a successful locking of mutex, and NIL on
timeout.
If the calling thread has already acquired mutex, then
mutex is recursive, T is
returned (for each recursive MT:MUTEX-LOCK there should be a
separate MT:MUTEX-UNLOCK);mutex is non-recursive an ERROR is SIGNALed to
avoid a deadlock.Cf. pthread_mutex_lock.
(MT:MUTEX-UNLOCK
mutex)Release (unlock) mutex. If the calling thread is
not locking mutex, an ERROR is SIGNALed.
Cf. pthread_mutex_unlock.
(MT:MUTEX-OWNER
mutex)Return the MT:THREAD that owns (locks) mutex,
or NIL if mutex is not locked.
By the time this function returns the mutex
ownership may have changed (unless the owner is the
MT:CURRENT-THREAD). The function is mostly useful for debugging
deadlocks.
(MT:MUTEX-RECURSIVE-P
mutex)mutex is recursive.
(MT:WITH-MUTEX-LOCK (mutex)
&BODY body)body with mutex locked.
Upon exit mutex is released. Return whatever body returns.
The type of the object returned by MT:MAKE-EXEMPTION.
These correspond to the POSIX condition variables,
see <pthread.h>.
These objects allow broadcasting state from one MT:THREAD to the others.
(MT:EXEMPTIONP
object)(MT:MAKE-EXEMPTION
&KEY :NAME)Create a new MT:EXEMPTION object.
:NAME should be a STRING describing the exemption (this really
helps debugging deadlocks).
Cf. pthread_cond_init.
(MT:EXEMPTION-NAME
exemption)name of the exemption.
(MT:EXEMPTION-SIGNAL
exemption)Signal exemption object, i.e. wake up a thread blocked
on waiting for exemption.
Cf. pthread_cond_signal.
(MT:EXEMPTION-WAIT
exemption mutex &KEY :TIMEOUT :TEST)Wait for another MT:THREAD to call MT:EXEMPTION-SIGNAL
or MT:EXEMPTION-BROADCAST on exemption.
mutex should be locked by the caller; otherwise an ERROR is SIGNALed.
The function releases the mutex and waits for exemption.
On return mutex is acquired again.
When using exemptions there is always a boolean predicate involving
shared variables associated with each exemption wait that is true if the
thread should proceed.
The function waits up to :TIMEOUT seconds.
If timeout is not specified, waits forever.
Returns T if exemption was signaled and NIL on timeout.
On POSIX it is possible to have
spurious
wakeups, i.e., this function may return T even though no
thread called MT:EXEMPTION-BROADCAST or MT:EXEMPTION-SIGNAL.
Therefore, a common idiom for using this function is: (LOOP
:until (predicate) :do (MT:EXEMPTION-WAIT exemption mutex))
The :TEST argument simplifies this. When supplied,
MT:EXEMPTION-WAIT returns when either :TEST predicate is satisfied
(always called while mutex is held) or when :TIMEOUT elapses.
The above loop is equivalent to: (.
When MT:EXEMPTION-WAIT exemption
mutex :TEST #'predicate):TEST is supplied, MT:EXEMPTION-WAIT returns T when exemption was
signaled and :TEST predicate is satisfied and NIL on timeout.
This is the preferred and most portable way to wait on an exemption.
Cf. pthread_cond_wait.
(MT:EXEMPTION-BROADCAST
exemption)Signal exemption to all threads waiting for it.
(MT:Y-OR-N-P-TIMEOUT
seconds default &REST arguments)(MT:YES-OR-NO-P-TIMEOUT seconds default &REST
arguments)Y-OR-N-P and YES-OR-NO-P, but use
MT:WITH-TIMEOUT to return DEFAULT when no
reply is given within timeout seconds.(MT:WITH-TIMEOUT
(seconds &BODY timeout-forms) &BODY body)Execute body. If it does not finish for up to
seconds, it is interrupted and timeout-forms
are executed.
Return the values of the last evaluated form in either
body or timeout-forms.
Since on timeout the current thread is interrupted,
special care may be needed for ensuring proper cleanup in body.
See MT:THREAD-INTERRUPT and MT:WITH-DEFERRED-INTERRUPTS.
(SETF
(MT:SYMBOL-VALUE-THREAD symbol thread) value)(MT:SYMBOL-VALUE-THREAD symbol thread)thread is T, use the MT:CURRENT-THREAD; if it is NIL, use the
Global Variable binding.
Returns two values: the symbol binding and an indicator: NIL if
not bound in the thread, T if bound, and SYMBOL MAKUNBOUND if
the Per-Thread Variable binding was removed with MAKUNBOUND.
MT:*DEFAULT-SPECIAL-BINDINGS*:INITIAL-BINDINGS
argument of MT:MAKE-THREAD.(MT:WITH-DEFERRED-INTERRUPTS &BODY body)Defer thread interrupts (but not thread
preemption) while body is executed. If there is an interrupt
while body is run, it is queued and will be executed after
body finishes.
Care is needed if waiting or blocking in body,
since there is no way to interrupt it (in case of a deadlock).
The macro was added to avoid partial UNWIND-PROTECT's cleanup
forms evaluation in case they are interrupted with a non-local exit.
(MT:THREAD-JOIN thread &KEY
:TIMEOUT)Wait for thread to terminate and return two values:
thread's multiple values as a LIST and a BOOLEAN indicator of whether thread
finished normally or has been interrupted with MT:THREAD-INTERRUPT.
This function uses MT:EXEMPTION-WAIT (and not
pthread_join), so there are no resource
leaks normally associated with this function.
On timeout, return multiple values NIL and :TIMEOUT.
This function can be used repeatedly on the same thread, so this is the usual way to access the return values of a finished thread.
| These notes document CLISP version 2.49.93+ | Last modified: 2018-02-19 |