--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 LOAD
ed before
any MT:THREADs are spawned.
Nothing is ever locked automatically (automatic locking will
impose an unjustifiable penalty on HASH-TABLE
s and SEQUENCE
s
local to threads), so the user must use locks when sharing
HASH-TABLE
s, SEQUENCE
s 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-STATE
RANDOM
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
(GETHASH
x global-ht 0)) ; see Section 32.5.2.5, “Hash Tables, Sequences, and other mutable objects” (SETF
(AREF
global-array ...) ...) ; ditto (DEFMETHOD
generic-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-TABLE
s
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-BINDINGS
an association list of (
.
The symbol
. form
)form
s are EVAL
uated in the context of the new thread
and symbol
s 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-SIZE
STACK
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 SIGNAL
ed.
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 SIGNAL
ed 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 SIGNAL
ed.
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 SIGNAL
ed.
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 |