32.5. Multiple Threads of Execution

Platform Dependent: Only in CLISP built with configure-time flag --with-threads.

32.5.1. Introduction
32.5.2. General principles
32.5.2.1. Parallelizability
32.5.2.2. Special Variable Values
32.5.2.3. Packages
32.5.2.4. CLOS
32.5.2.5. Hash Tables, Sequences, and other mutable objects
32.5.2.5.1. RANDOM and RANDOM-STATE
32.5.2.6. Examples of thread-unsafe code
32.5.3. Thread API reference

Warning

This functionality is experimental.

Use it at your own risk.

Discuss it on clisp-devel.

32.5.1. Introduction

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”.

32.5.2. General principles

32.5.2.1. Parallelizability

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

  • If A and B have no common objects, then the implementation ensures that the principle is fulfilled.
  • If A and B share some objects, the implementation allows the programs to satisfy the principle with little effort.

32.5.2.2. Special Variable Values

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 via MT: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))

32.5.2.3. Packages

Note

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.

32.5.2.4. CLOS

This information is likely to change in the near future

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.

32.5.2.5. Hash Tables, Sequences, and other mutable objects

If you want to shoot yourself, it is your responsibility to wear armor.

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]
32.5.2.5.1. 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.

32.5.2.6. Examples of thread-unsafe code

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

Warning

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.

32.5.3. Thread API reference

MT:THREAD

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 (symbol . form). The 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.

Note

When the same symbol appears in this association list multiple times, the first occurrence determines the value.

:CSTACK-SIZE
the size in bytes of the control (C) stack. If 0, the value is decided by the OS.
:VSTACK-SIZE
the size of the CLISP 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 SIGNALed.

Cf. pthread_create.

(MT:THREADP object)
Check whether the object is of type MT:THREAD.
(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 (MT:THREAD-INTERRUPT thread :FUNCTION NIL) to debug thread and (MT:THREAD-INTERRUPT thread :FUNCTION T) to terminate 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.

Warning

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)
Return the name of the thread.
(MT:THREAD-ACTIVE-P thread)

Return NIL if the thread has already terminated and T otherwise.

Warning

By the time this function returns T, thread may have already terminated anyway.

(MT:CURRENT-THREAD)
Return the MT:THREAD object encapsulating the caller.
(MT:LIST-THREADS)

Return the LIST of all currently running MT:THREADs.

Warning

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.

MT:MUTEX

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)
Check whether the object is of type MT:MUTEX.
(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)
Return the 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

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.

Warning

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)
Return a indicator whether mutex is recursive.
(MT:WITH-MUTEX-LOCK (mutex) &BODY body)
Execute body with mutex locked. Upon exit mutex is released. Return whatever body returns.
MT:EXEMPTION

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)
Check whether the object is of type MT:EXEMPTION.
(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)
Return the 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: (MT:EXEMPTION-WAIT exemption mutex :TEST #'predicate). When :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.

Cf. pthread_cond_broadcast.

(MT:Y-OR-N-P-TIMEOUT seconds default &REST arguments)
(MT:YES-OR-NO-P-TIMEOUT seconds default &REST arguments)
Similar to 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.

Warning

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)
Access or set the Per-Thread Variable value. When 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*
The default :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.

Warning

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