guile-user
[Top][All Lists]
Advanced

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

Some introductory docs about C level threading


From: Marius Vollmer
Subject: Some introductory docs about C level threading
Date: Fri, 21 Jan 2005 19:29:35 +0100
User-agent: Gnus/5.1003 (Gnus v5.10.3) Emacs/21.3 (gnu/linux)

Hi,

this describes the model for C level threading for Guile 1.8.  What do
you think?

4.3.4 Asynchronous Signals
--------------------------

You can not call libguile functions from handlers for POSIX signals, but
you can register Scheme handlers for POSIX signals such as `SIGINT'.
These handlers do not run during the actual signal delivery.  Instead,
they are run when the program (more precisely, the thread that the
handler has been registered for) reaches the next _safe point_.

   The libguile functions themselves have many such safe points.
Consequently, you must be prepared for arbitrary actions anytime you
call a libguile function.  For example, even `scm_cons' can contain a
safe point and when a signal handler is pending for your thread,
calling `scm_cons' will run this handler and anything might happen,
including a non-local exit although `scm_cons' would not ordinarily do
such a thing on its own.

   If you do not want to allow the running of asynchronous signal
handlers, you can block them temporarily with `scm_frame_block_asyncs',
for example.  See *Note System asyncs::.

   Since signal handling in Guile relies on safe points, you need to
make sure that your functions do offer enough of them.  Normally,
calling libguile functions in the normal course of action is all that
is needed.  But when a thread might spent a long time in a code section
that calls no libguile function, it is good to include explicit safe
points.  This can allow the user to interrupt your code with <C-c>, for
example.

   You can do this with the macro `SCM_TICK'.  This macro is
syntactically a statement.  That is, you could use it like this:

     while (1)
       {
         SCM_TICK;
         do_some_work ();
       }

   Frequent execution of a safe point is even more important in multi
threaded programs, *Note Multi-Threading::.


4.3.5 Multi-Threading
---------------------

Guile can be used in multi-threaded programs just as well as in
single-threaded ones.

   Each thread that wants to use functions from libguile must put itself
into _guile mode_ and must then follow a few rules.  If it doesn't want
to honer these rules in certain situations, a thread can temporarily
leave guile mode (but can no longer use libguile functions during that
time, of course).

   Threads enter guile mode by calling `scm_with_guile',
`scm_boot_guile', or `scm_init_guile'.  As explained in the reference
documentation for these functions, Guile will then learn about the
stack bounds of the thread and can protect the `SCM' values that are
stored in local variables.  When a thread puts itself into guile mode
for the first time, it gets a Scheme representation and is listed by
`all-threads', for example.

   While in guile mode, a thread promises to reach a safe point
reasonably frequently (*note Asynchronous Signals::).  In addition to
running signal handlers, these points are also potential rendezvous
points of all guile mode threads where Guile can orchestrate global
things like garbage collection.  Consequently, when a thread in guile
mode blocks and does no longer frequent safe points, it might cause all
other guile mode threads to block as well.  To prevent this from
happening, a guile mode thread should either only block in libguile
functions (who know how to do it right), or should temporarily leave
guile mode with `scm_without_guile' or
`scm_leave_guile'/`scm_enter_guile'.

   For some comming blocking operations, Guile provides convenience
functions.  For example, if you want to lock a pthread mutex while in
guile mode, you might want to use `scm_pthread_mutex_lock' which is
just like `pthread_mutex_lock' except that it leaves guile mode while
blocking.

   All libguile functions are (intended to be) robust in the face of
multiple threads using them concurrently.  This means that there is no
risk of the internal data structures of libguile becoming corrupted in
such a way that the process crashes.

   A program might still produce non-sensical results, though.  Taking
hash tables as an example, Guile guarantees that you can use them from
multiple threads concurrently and a hashtable will always remain a valid
hashtable and Guile will not crash when you access it.  It does not
guarantee, however, that inserting into it concurrently from two threads
will give useful results: only one insertion might actually happen, none
might happen, or the table might in general be modified in a totally
arbitrary manner.  (It will still be a valid hashtable, but not the one
that you might have expected.)

   Thus, you need to put in additional synchronizations when multiple
threads want to use a single hashtable, or any other mutable Scheme
object.

   When writing C code for use with libguile, you should try to make it
robust as well.  An example that converts a list into a vector will help
to illustrate.  Here is a correct version:

     SCM
     my_list_to_vector (SCM list)
     {
       SCM vector = scm_make_vector (scm_length (list), SCM_UNDEFINED);
       size_t len, i;

       len = SCM_SIMPLE_VECTOR_LENGTH (vector);
       i = 0;
       while (i < len && scm_is_pair (list))
         {
           SCM_SIMPLE_VECTOR_SET (vector, i, SCM_CAR (list));
           list = SCM_CDR (list);
           i++;
         }

       return vector;
     }

   The first thing to note is that storing into a `SCM' location
concurrently from multiple threads is guaranteed to be robust: you don't
know which value wins but it will in any case be a valid `SCM' value.

   But there is no guarantee that the list referenced by LIST is not
modified in another thread while the loop iterates over it.  Thus, while
copying its elements into the vector, the list might get longer or
shorter.  For this reason, the loop must check both that it doesn't
overrun the vector (`SCM_SIMPLE_VECTOR_SET' does no range-checking) and
that it doesn't overrung the list (`SCM_CAR' and `SCM_CDR' likewise do
no type checking).

   It is safe to use `SCM_CAR' and `SCM_CDR' on the local variable LIST
once it is known that the variable contains a pair.  The contents of
the pair might change spontaneously, but it will always stay a valid
pair (and a local variable will of course not spontaneously point to a
different Scheme object).

   Likewise, a simple vector such as the one returned by
`scm_make_vector' is guaranteed to always stay the same length so that
it is safe to only use SCM_SIMPLE_VECTOR_LENGTH once and store the
result.  (In the example, VECTOR is safe anyway since it is a fresh
object that no other thread can possibly know about until it is
returned from `my_list_to_vector'.)

   Of course the behavior of `my_list_to_vector' is suboptimal when
LIST does indeed gets asynchronously lengthened or shortened in another
thread.  But it is robust: it will always return a valid vector.  That
vector might be shorter than expected, or its last elements might be
unspecified, but it is a valid vector and if a program wants to rule
out these cases, it must avoid modifiying the list asynchronously.

   Here is another version that is also correct:

     SCM
     my_pedantic_list_to_vector (SCM list)
     {
       SCM vector = scm_make_vector (scm_length (list), SCM_UNDEFINED);
       size_t len, i;

       len = SCM_SIMPLE_VECTOR_LENGTH (vector);
       i = 0;
       while (i < len)
         {
           SCM_SIMPLE_VECTOR_SET (vector, i, scm_car (list));
           list = scm_cdr (list);
           i++;
         }

       return vector;
     }

   This version uses the type-checking and thread-robust functions
`scm_car' and `scm_cdr' instead of the faster, but less robust macros
`SCM_CAR' and `SCM_CDR'.  When the list is shortened (that is, when
LIST holds a non-pair), `scm_car' will throw an error.  This might be
preferable to just returning a half-initialized vector.





reply via email to

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