summaryrefslogtreecommitdiff
path: root/doc/ref/api-scheduling.texi
diff options
context:
space:
mode:
Diffstat (limited to 'doc/ref/api-scheduling.texi')
-rw-r--r--doc/ref/api-scheduling.texi145
1 files changed, 108 insertions, 37 deletions
diff --git a/doc/ref/api-scheduling.texi b/doc/ref/api-scheduling.texi
index 2fb7d15bf..9b6e44088 100644
--- a/doc/ref/api-scheduling.texi
+++ b/doc/ref/api-scheduling.texi
@@ -336,23 +336,105 @@ checking if the return value is @code{eq?} to @var{expected}.
@cindex mutex
@cindex condition variable
-A mutex is a thread synchronization object, it can be used by threads
-to control access to a shared resource. A mutex can be locked to
-indicate a resource is in use, and other threads can then block on the
-mutex to wait for the resource (or can just test and do something else
-if not available). ``Mutex'' is short for ``mutual exclusion''.
-
-There are two types of mutexes in Guile, ``standard'' and
-``recursive''. They're created by @code{make-mutex} and
-@code{make-recursive-mutex} respectively, the operation functions are
-then common to both.
-
-Note that for both types of mutex there's no protection against a
-``deadly embrace''. For instance if one thread has locked mutex A and
-is waiting on mutex B, but another thread owns B and is waiting on A,
-then an endless wait will occur (in the current implementation).
-Acquiring requisite mutexes in a fixed order (like always A before B)
-in all threads is one way to avoid such problems.
+Mutexes are low-level primitives used to coordinate concurrent access to
+mutable data. Short for ``mutual exclusion'', the name ``mutex''
+indicates that only one thread at a time can acquire access to data that
+is protected by a mutex -- threads are excluded from accessing data at
+the same time. If one thread has locked a mutex, then another thread
+attempting to lock that same mutex will wait until the first thread is
+done.
+
+Mutexes can be used to build robust multi-threaded programs that take
+advantage of multiple cores. However, they provide very low-level
+functionality and are somewhat dangerous; usually you end up wanting to
+acquire multiple mutexes at the same time to perform a multi-object
+access, but this can easily lead to deadlocks if the program is not
+carefully written. For example, if objects A and B are protected by
+associated mutexes M and N, respectively, then to access both of them
+then you need to acquire both mutexes. But what if one thread acquires
+M first and then N, at the same time that another thread acquires N them
+M? You can easily end up in a situation where one is waiting for the
+other.
+
+There's no easy way around this problem on the language level. A
+function A that uses mutexes does not necessarily compose nicely with a
+function B that uses mutexes. For this reason we suggest using atomic
+variables when you can (@pxref{Atomics}), as they do not have this problem.
+
+Still, if you as a programmer are responsible for a whole system, then
+you can use mutexes as a primitive to provide safe concurrent
+abstractions to your users. (For example, given all locks in a system,
+if you establish an order such that M is consistently acquired before N,
+you can avoid the ``deadly-embrace'' deadlock described above. The
+problem is enumerating all mutexes and establishing this order from a
+system perspective.) Guile gives you the low-level facilities to build
+such systems.
+
+In Guile there are additional considerations beyond the usual ones in
+other programming languages: non-local control flow and asynchronous
+interrupts. What happens if you hold a mutex, but somehow you cause an
+exception to be thrown? There is no one right answer. You might want
+to keep the mutex locked to prevent any other code from ever entering
+that critical section again. Or, your critical section might be fine if
+you unlock the mutex ``on the way out'', via a catch handler or
+@code{dynamic-wind}. @xref{Catch}, and @xref{Dynamic Wind}.
+
+But if you arrange to unlock the mutex when leaving a dynamic extent via
+@code{dynamic-wind}, what to do if control re-enters that dynamic extent
+via a continuation invocation? Surely re-entering the dynamic extent
+without the lock is a bad idea, so there are two options on the table:
+either prevent re-entry via @code{with-continuation-barrier} or similar,
+or reacquiring the lock in the entry thunk of a @code{dynamic-wind}.
+
+You might think that because you don't use continuations, that you don't
+have to think about this, and you might be right. If you control the
+whole system, you can reason about continuation use globally. Or, if
+you know all code that can be called in a dynamic extent, and none of
+that code can call continuations, then you don't have to worry about
+re-entry, and you might not have to worry about early exit either.
+
+However, do consider the possibility of asynchronous interrupts
+(@pxref{Asyncs}). If the user interrupts your code interactively, that
+can cause an exception; or your thread might be cancelled, which does
+the same; or the user could be running your code under some pre-emptive
+system that periodically causes lightweight task switching. (Guile does
+not currently include such a system, but it's possible to implement as a
+library.) Probably you also want to defer asynchronous interrupt
+processing while you hold the mutex, and probably that also means that
+you should not hold the mutex for very long.
+
+All of these additional Guile-specific considerations mean that from a
+system perspective, you would do well to avoid these hazards if you can
+by not requiring mutexes. Instead, work with immutable data that can be
+shared between threads without hazards, or use persistent data
+structures with atomic updates based on the atomic variable library
+(@pxref{Atomics}).
+
+There are three types of mutexes in Guile: ``standard'', ``recursive'',
+and ``unowned''.
+
+Calling @code{make-mutex} with no arguments makes a standard mutex. A
+standard mutex can only be locked once. If you try to lock it again
+from the thread that locked it to begin with (the "owner" thread), it
+throws an error. It can only be unlocked from the thread that locked it
+in the first place.
+
+Calling @code{make-mutex} with the symbol @code{recursive} as the
+argument, or calling @code{make-recursive-mutex}, will give you a
+recursive mutex. A recursive mutex can be locked multiple times by its
+owner. It then has to be unlocked the corresponding number of times,
+and like standard mutexes can only be unlocked by the owner thread.
+
+Finally, calling @code{make-mutex} with the symbol
+@code{allow-external-unlock} creates an unowned mutex. An unowned mutex
+is like a standard mutex, except that it can be unlocked by any thread.
+A corrolary of this behavior is that a thread's attempt to lock a mutex
+that it already owns will block instead of signalling an error, as it
+could be that some other thread unlocks the mutex, allowing the owner
+thread to proceed. This kind of mutex is a bit strange and is here for
+use by SRFI-18.
+
+The mutex procedures in Guile can operate on all three kinds of mutexes.
To use these facilities, load the @code{(ice-9 threads)} module.
@@ -361,25 +443,14 @@ To use these facilities, load the @code{(ice-9 threads)} module.
@end example
@sp 1
-@deffn {Scheme Procedure} make-mutex flag @dots{}
+@deffn {Scheme Procedure} make-mutex [kind]
@deffnx {C Function} scm_make_mutex ()
-@deffnx {C Function} scm_make_mutex_with_flags (SCM flags)
-Return a new mutex. It is initially unlocked. If @var{flag} @dots{} is
-specified, it must be a list of symbols specifying configuration flags
-for the newly-created mutex. The supported flags are:
-@table @code
-@item unchecked-unlock
-Unless this flag is present, a call to `unlock-mutex' on the returned
-mutex when it is already unlocked will cause an error to be signalled.
-
-@item allow-external-unlock
-Allow the returned mutex to be unlocked by the calling thread even if
-it was originally locked by a different thread.
-
-@item recursive
-The returned mutex will be recursive.
-
-@end table
+@deffnx {C Function} scm_make_mutex_with_kind (SCM kind)
+Return a new mutex. It will be a standard non-recursive mutex, unless
+the @code{recursive} symbol is passed as the optional @var{kind}
+argument, in which case it will be recursive. It's also possible to
+pass @code{unowned} for semantics tailored to SRFI-18's use case; see
+above for details.
@end deffn
@deffn {Scheme Procedure} mutex? obj
@@ -391,8 +462,8 @@ Return @code{#t} if @var{obj} is a mutex; otherwise, return
@deffn {Scheme Procedure} make-recursive-mutex
@deffnx {C Function} scm_make_recursive_mutex ()
Create a new recursive mutex. It is initially unlocked. Calling this
-function is equivalent to calling `make-mutex' and specifying the
-@code{recursive} flag.
+function is equivalent to calling @code{make-mutex} with the
+@code{recursive} kind.
@end deffn
@deffn {Scheme Procedure} lock-mutex mutex [timeout [owner]]