[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Guile-commits] 20/24: Update mutex documentation

From: Andy Wingo
Subject: [Guile-commits] 20/24: Update mutex documentation
Date: Sun, 6 Nov 2016 18:00:46 +0000 (UTC)

wingo pushed a commit to branch master
in repository guile.

commit 8d907758a0a0d4e4a02ccb91e56ba6e42c6a747f
Author: Andy Wingo <address@hidden>
Date:   Sat Nov 5 19:38:40 2016 +0100

    Update mutex documentation
    * doc/ref/api-scheduling.texi (Mutexes and Condition Variables): Add
      foreboding preface.
 doc/ref/api-scheduling.texi |  145 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 108 insertions(+), 37 deletions(-)

diff --git a/doc/ref/api-scheduling.texi b/doc/ref/api-scheduling.texi
index 2fb7d15..9b6e440 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 
 @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
address@hidden 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
+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
+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
address@hidden  @xref{Catch}, and @xref{Dynamic Wind}.
+But if you arrange to unlock the mutex when leaving a dynamic extent via
address@hidden, 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
+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
address@hidden 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)} 
 @end example
 @sp 1
address@hidden {Scheme Procedure} make-mutex flag @dots{}
address@hidden {Scheme Procedure} make-mutex [kind]
 @deffnx {C Function} scm_make_mutex ()
address@hidden {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:
address@hidden @code
address@hidden 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.
address@hidden allow-external-unlock
-Allow the returned mutex to be unlocked by the calling thread even if
-it was originally locked by a different thread.
address@hidden recursive
-The returned mutex will be recursive.
address@hidden table
address@hidden {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
address@hidden flag.
+function is equivalent to calling @code{make-mutex} with the
address@hidden kind.
 @end deffn
 @deffn {Scheme Procedure} lock-mutex mutex [timeout [owner]]

reply via email to

[Prev in Thread] Current Thread [Next in Thread]