Next: Signals and Interrupts, Previous: Foreign Function Interface, Up: Extensions [Contents][Index]
Next: Processes (native threads), Up: Native threads [Contents][Index]
On most platforms, ECL supports native multithreading. That means there can be several tasks executing lisp code on parallel and sharing memory, variables and files. The interface for multitasking in ECL, like those of most other implementations, is based on a set of functions and types that resemble the multiprocessing capabilities of old Lisp Machines.
This backward compatibility is why tasks or threads are called "processes". However, they should not be confused with operating system processes, which are made of programs running in separate contexts and without access to each other’s memory.
The implementation of threads in ECL is purely native and based on Posix Threads wherever available. The use of native threads has advantages. For instance, they allow for non-blocking file operations, so that while one task is reading a file, a different one is performing a computation.
As mentioned above, tasks share the same memory, as well as the set of open files and sockets. This manifests on two features. First of all, different tasks can operate on the same lisp objects, reading and writing their slots, or manipulating the same arrays. Second, while threads share global variables, constants and function definitions they can also have thread-local bindings to special variables that are not seen by other tasks.
The fact that different tasks have access to the same set of data allows both for flexibility and a greater risk. In order to control access to different resources, ECL provides the user with locks, as explained in the next section.
Next: Processes dictionary, Previous: Introduction to native threads, Up: Native threads [Contents][Index]
Process is a primitive representing native thread.
Next: Locks (mutexes), Previous: Processes (native threads), Up: Native threads [Contents][Index]
Returns the list of processes associated to running tasks. The list is a fresh new one and can be destructively modified. However, it may happen that the output list is not up to date, because some of the tasks have expired before this copy is returned.
When called from a running task, this function immediately causes the
task to finish. When invoked from the main thread, it is equivalent to
invoking ext:quit
with exit code 0.
Interrupt a task. This function sends a signal to a running process. When the task is free to process that signal, it will stop whatever it is doing and execute the given function.
WARNING: Use with care! Interrupts can happen anywhere,
except in code regions explicitely protected with
mp:without-interrupts
. This can lead to dangerous situations
when interrupting functions which are not thread safe. In particular,
one has to consider:
throw
or return-from
in the interrupting code will handle unwind-protect
forms like usual. However, the cleanup forms of an unwind-protect
can still be interrupted. In that case the execution flow will jump to the next unwind-protect
.
Example:
Kill a task that is doing nothing (See mp:process-kill
).
(flet ((task-to-be-killed () ;; Infinite loop (loop (sleep 1)))) (let ((task (mp:process-run-function 'background #'task-to-be-killed))) (sleep 10) (mp:interrupt-process task 'mp:exit-process)))
Create a new thread. This function creates a separate task with a name
set to name and no function to run. See also
mp:process-run-function
. Returns newly created process.
If initial-bindings is false, the new process inherits local
bindings to special variables (i.e. binding a special variable with
let
or let*
) from the current thread, otherwise the new
thread possesses no local bindings.
Returns t
when process is active, nil
otherwise. Signals an error if process doesn’t designate a valid
process.
The argument to this function should be a process created by
mp:make-process
, which has a function associated as per
mp:process-preset
but which is not yet running. After invoking
this function a new thread will be created in which the associated
function will be executed. Returns process if the thread
creation was successful and nil
otherwise.
(defun process-run-function (process-name process-function &rest args) (let ((process (mp:make-process name))) (apply #'mp:process-preset process function args) (mp:process-enable process)))
Yield the processor to other threads.
Suspend current thread until process exits. Return the result values of the process function.
Try to stop a running task. Killing a process may fail if the task has disabled interrupts.
Example:
Kill a task that is doing nothing
(flet ((task-to-be-killed () ;; Infinite loop (loop (sleep 1)))) (let ((task (mp:process-run-function 'background #'task-to-be-killed))) (sleep 10) (mp:process-kill task)))
Suspend a running process. May be resumed with
mp:process-resume
.
Example:
(flet ((ticking-task () ;; Infinite loop (loop (sleep 1) (print :tick)))) (print "Running task (one tick per second)") (let ((task (mp:process-run-function 'background #'ticking-task))) (sleep 5) (print "Suspending task for 5 seconds") (mp:process-suspend task) (sleep 5) (print "Resuming task for 5 seconds") (mp:process-resume task) (sleep 5) (print "Killing task") (mp:process-kill task)))
Resumes a suspended process. See example in
mp:process-suspend
.
Returns the name of a process (if any).
Associates a function to call with the arguments function-args, with a stopped process. The function will be the entry point when the task is enabled in the future.
See mp:process-enable
and mp:process-run-function
.
Create a new process using mp:make-process
, associate a function
to it and start it using mp:process-preset
.
Example:
(flet ((count-numbers (end-number) (dotimes (i end-number) (format t "~%;;; Counting: ~i" i) (terpri) (sleep 1)))) (mp:process-run-function 'counter #'count-numbers 10))
Returns/holds the current process of a caller.
Blocks process for interrupts and returns the previous sigmask.
See mp:interrupt-process
.
Enables the interrupts from sigmask.
See mp:interrupt-process
.
Executes body with all deferrable interrupts disabled. Deferrable interrupts arriving during execution of the body take effect after body has been executed.
Deferrable interrupts include most blockable POSIX signals, and
mp:interrupt-process
. Does not interfere with garbage
collection, and unlike in many traditional Lisps using userspace
threads, in ECL mp:without-interrupts
does not inhibit
scheduling of other threads.
Binds mp:allow-with-interrupts
,
mp:with-local-interrupts
and
mp:with-restored-interrupts
as a local macros.
mp:with-restored-interrupts
executes the body with interrupts
enabled if and only if the mp:without-interrupts
was in an
environment in which interrupts were allowed.
mp:allow-with-interrupts
allows the
mp:with-interrupts
to take effect during the dynamic scope of
its body, unless there is an outer mp:without-interrupts
without a corresponding mp:allow-with-interrupts
.
mp:with-local-interrupts
executes its body with interrupts
enabled provided that there is an mp:allow-with-interrupts
for
every mp:without-interrupts
surrounding the current one.
mp:with-local-interrupts
is equivalent to:
(mp:allow-with-interrupts (mp:with-interrupts ...))
Care must be taken not to let either mp:allow-with-interrupts
or mp:with-local-interrupts
appear in a function that escapes
from inside the mp:without-interrupts
in:
(mp:without-interrupts ;; The body of the lambda would be executed with WITH-INTERRUPTS allowed ;; regardless of the interrupt policy in effect when it is called. (lambda () (mp:allow-with-interrupts ...))) (mp:without-interrupts ;; The body of the lambda would be executed with interrupts enabled ;; regardless of the interrupt policy in effect when it is called. (lambda () (mp:with-local-interrupts ...)))
Executes body with deferrable interrupts conditionally enabled. If there are pending interrupts they take effect prior to executing body.
As interrupts are normally allowed mp:with-interrupts
only
makes sense if there is an outer mp:without-interrupts
with a
corresponding mp:allow-with-interrupts
: interrupts are not
enabled if any outer mp:without-interrupts
is not accompanied
by mp:allow-with-interrupts
.
Next: Locks dictionary, Previous: Processes dictionary, Up: Native threads [Contents][Index]
Locks are used to synchronize access to the shared data. Lock may be owned only by a single thread at any given time. Recursive locks may be re-acquired by the same thread multiple times (and non-recursive locks can’t).
Next: Readers-writer locks, Previous: Locks (mutexes), Up: Native threads [Contents][Index]
C/C++ equivalent of mp:make-lock
without key arguments.
See mp:make-lock
.
Creates a lock named name. If recursive is true, a recursive lock is created that can be locked multiple times by the same thread.
Predicate verifying if lock is recursive.
Predicate verifying if the current thread holds lock.
Returns the name of lock.
Returns the process owning lock. For testing whether the current
thread is holding a lock see mp:holding-lock-p
.
Returns number of processes waiting for lock.
Grabs a lock (blocking if lock is already taken). Returns
ECL_T
.
Grabs a lock if free (non-blocking). If lock is already taken
returns ECL_NIL
, otherwise ECL_T
.
Tries to acquire a lock. wait indicates whether function should
block or give up if lock is already taken. If wait is
nil
and lock can’t be acquired returns
nil
. Succesful operation returns t
.
Releases lock.
Acquire lock for the dynamic scope of body, which is executed with the lock held by current thread. Returns the values of body.
Next: Readers-writer locks dictionary, Previous: Locks dictionary, Up: Native threads [Contents][Index]
Readers-writer (or shared-exclusive ) locks allow concurrent access
for read-only operations, while write operations require exclusive
access. mp:rwlock
is non-recursive.
Readers-writers locks are an optional feature, which is available if
*features*
includes :ecl-read-write-lock
.
Next: Condition variables, Previous: Readers-writer locks, Up: Native threads [Contents][Index]
C/C++ equivalent of mp:make-rwlock
without key
arguments.
See mp:make-rwlock
.
Creates a rwlock named name.
Returns the name of lock.
Acquires lock (blocks if lock is already taken with
mp:get-rwlock-write
. Lock may be acquired by multiple
readers). Returns ECL_T
.
Tries to acquire lock. If lock is already taken with
mp:get-rwlock-write
returns ECL_NIL
, otherwise
ECL_T
.
Tries to acquire lock. wait indicates whenever function
should block or give up if lock is already taken with
mp:get-rwlock-write
.
Acquires lock (blocks if lock is already taken). Returns
ECL_T
.
Tries to acquire lock. If lock is already taken returns
ECL_NIL
, otherwise ECL_T
.
Tries to acquire lock. wait indicates whenever function should block or give up if lock is already taken.
Release lock.
Acquire rwlock for the dynamic scope of body for operation operation, which is executed with the lock held by current thread. Returns the values of body.
Valid values of argument operation are :read
or
:write
(for reader and writer access accordingly).
Next: Condition variables dictionary, Previous: Readers-writer locks dictionary, Up: Native threads [Contents][Index]
Condition variables are used to wait for a particular condition becoming true (e.g new client connects to the server).
Next: Semaphores, Previous: Condition variables, Up: Native threads [Contents][Index]
Creates a condition variable.
Release lock and suspend thread until condition
mp:condition-variable-signal
is called on cv. When thread
resumes re-aquire lock.
mp:condition-variable-wait
which timeouts after seconds
seconds.
Signal cv (wakes up only one waiter). After signal, signaling thread keeps lock, waking thread goes on the queue waiting for the lock.
Signal cv (wakes up all waiters).
Next: Semaphores dictionary, Previous: Condition variables dictionary, Up: Native threads [Contents][Index]
Semaphores are objects which allow an arbitrary resource count. Semaphores are used for shared access to resources where number of concurrent threads allowed to access it is limited.
Next: Barriers, Previous: Semaphores, Up: Native threads [Contents][Index]
C/C++ equivalent of mp:make-semaphore
without key
arguments.
See mp:make-semaphore
.
Creates a counting semaphore name with a resource count count.
Returns the name of semaphore.
Returns the resource count of semaphore.
Returns the number of threads waiting on semaphore.
Waits on semaphore until it can grab the resource (blocking). Returns resource count before semaphore was acquired.
Tries to get a semaphore (non-blocking). If there is no resource left
returns nil
, otherwise returns resource count before semaphore
was acquired.
Releases count units of a resource on semaphore.
Next: Barriers dictionary, Previous: Semaphores dictionary, Up: Native threads [Contents][Index]
Barriers are objects which for a group of threads make them stop and they can’t proceed until all other threads reach the barrier.
Next: Atomic operations, Previous: Barriers, Up: Native threads [Contents][Index]
C/C++ equivalent of mp:make-barrier
without key
arguments.
See mp:make-barrier
.
Creates a barrier name with a thread count count.
Returns the count of barrier.
Returns the name of barrier.
Returns the number of threads waiting on barrier.
The caller thread waits on barrier. When the barrier is saturated then all threads waiting on it are unblocked.
Forcefully wakes up all processes waiting on the barrier.
reset-count when used resets barrier counter.
disable disables or enables barrier. When a barrier is
disabled then all calls to mp:barrier-wait
immedietely
return.
kill-waiting is used to kill all woken threads.
Next: Atomic operations dictionary, Previous: Barriers dictionary, Up: Native threads [Contents][Index]
ECL supports both compare-and-swap and fetch-and-add (which may be
faster on some processors) atomic operations on a number of different
places. The compare-and-swap macro is user extensible with a protocol
similar to setf
.
Previous: Atomic operations, Up: Native threads [Contents][Index]
Perform an atomic compare and swap operation on slot and return
the previous value stored in slot. If the return value is equal
to old (comparison by ==
), the operation has succeeded.
This is a inline-only function defined in "ecl/ecl_atomics.h".
Atomically increment slot by the given increment and return the
previous value stored in slot. The consequences are undefined if
the value of slot is not of type fixnum
.
ecl_atomic_incf
signals an error if increment is not of
type fixnum
. This is a inline-only function defined in
"ecl/ecl_atomics.h".
Atomically increment slot by 1 and return the new value stored in slot.
Perform a volatile load of the object in slot and then
atomically set slot to ECL_NIL
. Returns the value
previously stored in slot.
Like push/pop but atomic.
Atomically increments/decrements the fixnum stored in place by
the given increment and returns the value of place before
the increment. Incrementing and decrementing is done using modular
arithmetic, so that mp:atomic-incf
of a place whose value is
most-positive-fixnum
by 1 results in
most-negative-fixnum
stored in place.
Currently the following places are supported:
car
, cdr
, first
, rest
, svref
,
symbol-value
, slot-value
,
clos:standard-instance-access
,
clos:funcallable-standard-instance-access
.
For slot-value
, the object should have no applicable methods
defined for slot-value-using-class
or (setf
slot-value-using-class)
.
The consequences are undefined if the value of place is not of
type fixnum
.
Atomically stores new in place if old is eq
to the current value of place. Returns the previous value of
place: if the returned value is eq
to old, the swap
was carried out.
Currently, the following places are supported:
car
, cdr
, first
, rest
, svref
,
symbol-plist
, symbol-value
, slot-value
,
clos:standard-instance-access
,
clos:funcallable-standard-instance-access
, a structure slot
accessor3 or any other place for which a compare-and-swap
expansion was defined by mp:defcas
or
mp:define-cas-expander
.
For slot-value
, slot-unbound
is called if the slot is
unbound unless old is eq
to si:unbound
, in which
case old is returned and new is assigned to the slot.
Additionally, the object should have no applicable methods defined for
slot-value-using-class
or (setf slot-value-using-class)
.
Atomically updates the CAS-able place to the value returned by
calling update-fn with arguments and the old value of
place. update-fn must be a function accepting (1+
(length arguments))
arguments. Returns the new value which was stored
in place.
place may be read and update-fn may be called more than once if multiple threads are trying to write to place at the same time.
Example:
Atomic update of a structure slot. If the update would not be atomic, the result would be unpredictable.
(defstruct test-struct (slot1 0)) (let ((struct (make-test-struct))) (mapc #'mp:process-join (loop repeat 100 collect (mp:process-run-function "" (lambda () (loop repeat 1000 do (mp:atomic-update (test-struct-slot1 struct) #'1+) (sleep 0.00001)))))) (test-struct-slot1 struct)) => 100000
Like push
/pop
, but atomic. place must be CAS-able
and may be read multiple times before the update succeeds.
Define a compare-and-swap expander similar to
define-setf-expander
. Defines the compare-and-swap-expander for
generalized-variables (accessor ...)
. When a form
(mp:compare-and-swap (accessor arg1 ... argn) old new)
is
evaluated, the forms given in the body of
mp:define-cas-expander
are evaluated in order with the
parameters in lambda-list
bound to arg1 ... argn
. The
body must return six values
(var1 ... vark) (form1 ... formk) old-var new-var compare-and-swap-form volatile-access-form
in order (Note that old-var
and new-var
are single
variables, unlike in define-setf-expander
). The whole
compare-and-swap
form is then expanded into
(let* ((var1 from1) ... (vark formk) (old-var old-form) (new-var new-form)) compare-and-swap-form).
Note that it is up to the user of this macro to ensure atomicity for the resulting compare-and-swap expansions.
Example
mp:define-cas-expander
can be used to define a more
convienient compare-and-swap expansion for a class slot. Consider the
following class:
(defclass food () ((name :initarg :name) (deliciousness :initform 5 :type '(integer 0 10) :accessor food-deliciousness))) (defvar *spätzle* (make-instance 'food :name "Spätzle"))
We can’t just use mp:compare-and-swap
on
*spätzle*
:
> (mp:compare-and-swap (food-deliciousness *x*) 5 10) Condition of type: SIMPLE-ERROR Cannot get the compare-and-swap expansion of (FOOD-DELICIOUSNESS *X*).
We can use symbol-value
, but let’s define a more convenient
compare-and-swap expander:
(mp:define-cas-expander food-deliciousness (food) (let ((old (gensym)) (new (gensym))) (values nil nil old new `(progn (check-type ,new (integer 0 10)) (mp:compare-and-swap (slot-value ,food 'deliciousness) ,old ,new)) `(food-deliciousness ,food))))
Now finally, we can safely store our rating:
> (mp:compare-and-swap (food-deliciousness *spätzle*) 5 10) 5
Define a compare-and-swap expansion similar to the short form
of defsetf
. Defines an expansion
(compare-and-swap (accessor arg1 ... argn) old new) => (cas-fun arg1 ... argn old new)
Note that it is up to the user of this macro to ensure atomicity for the resulting compare-and-swap expansions.
Remove a compare-and-swap expansion. It is an equivalent of
fmakunbound (setf symbol)
for cas expansions.
Returns the compare-and-swap expansion forms and variables as defined in
mp:define-cas-expander
for place as six values.
The creation of atomic structure slot accessors can be
deactivated by supplying a (:atomic-accessors nil)
option to
defstruct
.
Previous: Atomic operations, Up: Native threads [Contents][Index]