Version: 4.2.1
8 Threads
The initializer function scheme_basic_env creates the main
Scheme thread; all other threads are created through calls to
scheme_thread.
Information about each internal Scheme thread is kept in a
Scheme_Thread structure. A pointer to the current thread’s
structure is available as scheme_current_thread. A
Scheme_Thread structure includes the following fields:
error_buf – the mz_jmp_buf value used to escape
from errors. The error_buf value of the current thread is
available as scheme_error_buf.
cjs.jumping_to_continuation – a flag that
distinguishes escaping-continuation invocations from error
escapes. The cjs.jumping_to_continuation value of the current
thread is available as scheme_jumping_to_continuation.
init_config –
the thread’s initial parameterization. See also Parameterizations.
cell_values – The thread’s values for thread cells
(see also Parameterizations).
next – The next thread in the linked list of threads;
this is NULL for the main thread.
The list of all scheduled threads is kept in a linked list;
scheme_first_thread points to the first thread in the list.
The last thread in the list is always the main thread.
8.1 Integration with Threads
Scheme’s threads can break external C code under two circumstances:
Pointers to stack-based values can be communicated
between threads. For example, if thread A stores a pointer to a
stack-based variable in a global variable, if thread B uses the
pointer in the global variable, it may point to data that is not
currently on the stack.
C functions that can invoke Scheme (and also be invoked
by Scheme) depend on strict function-call nesting. For example,
suppose a function F uses an internal stack, pushing items on to the
stack on entry and popping the same items on exit. Suppose also that
F invokes Scheme to evaluate an expression. If the evaluation of
this expression invokes F again in a new thread, but then returns to
the first thread before completing the second F, then F’s internal
stack will be corrupted.
If either of these circumstances occurs, Scheme will probably crash.
8.2 Allowing Thread Switches
C code that performs substantial or unbounded work should occasionally
call SCHEME_USE_FUEL – actually a macro – which allows Scheme
to swap in another Scheme thread to run, and to check for breaks on
the current thread. In particular, if breaks are enabled, then
SCHEME_USE_FUEL may trigger an exception.
The macro consumes an integer argument. On most platforms, where
thread scheduling is based on timer interrupts, the argument is
ignored. On some platforms, however, the integer represents the amount
of “fuel” that has been consumed since the last call to
SCHEME_USE_FUEL. For example, the implementation of
vector->list consumes a unit of fuel for each created cons
cell:
Scheme_Object *scheme_vector_to_list(Scheme_Object *vec) |
{ |
int i; |
Scheme_Object *pair = scheme_null; |
|
i = SCHEME_VEC_SIZE(vec); |
|
for (; i--; ) { |
SCHEME_USE_FUEL(1); |
pair = scheme_make_pair(SCHEME_VEC_ELS(vec)[i], pair); |
} |
|
return pair; |
} |
The SCHEME_USE_FUEL macro expands to a C block, not an
expression.
8.3 Blocking the Current Thread
Embedding or extension code sometimes needs to block, but blocking
should allow other Scheme threads to execute. To allow other threads
to run, block using scheme_block_until. This procedure takes
two functions: a polling function that tests whether the blocking
operation can be completed, and a prepare-to-sleep function that sets
bits in fd_sets when Scheme decides to sleep (because all Scheme
threads are blocked). Under Windows, an “fd_set” can also
accommodate OS-level semaphores or other handles via
scheme_add_fd_handle.
Since the functions passed to scheme_block_until are called by
the Scheme thread scheduler, they must never raise exceptions, call
scheme_apply, or trigger the evaluation of Scheme code in any
way. The scheme_block_until function itself may call the current
exception handler, however, in reaction to a break (if breaks are
enabled).
When a blocking operation is associated with an object, then the
object might make sense as an argument to sync. To
extend the set of objects accepted by sync, either register
polling and sleeping functions with scheme_add_evt, or register
a semaphore accessor with scheme_add_evt_through_sema.
The scheme_signal_received function can be called to wake up
Scheme when it is sleeping. In particular, calling
scheme_signal_received ensures that Scheme will poll all
blocking synchronizations soon afterward. Furthermore,
scheme_signal_received can be called from any OS-level thread.
Thus, when no adequate prepare-to-sleep function can be implemented
for scheme_block_until in terms of file descriptors or Windows
handles, calling scheme_signal_received when the poll result
changes will ensure that a poll is issued.
8.4 Threads in Embedded Scheme with Event Loops
When Scheme is embedded in an application with an event-based model
(i.e., the execution of Scheme code in the main thread is repeatedly
triggered by external events until the application exits) special
hooks must be set to ensure that non-main threads execute
correctly. For example, during the execution in the main thread, a new
thread may be created; the new thread may still be running when the
main thread returns to the event loop, and it may be arbitrarily long
before the main thread continues from the event loop. Under such
circumstances, the embedding program must explicitly allow Scheme to
execute the non-main threads; this can be done by periodically calling
the function scheme_check_threads.
Thread-checking only needs to be performed when non-main threads exist
(or when there are active callback triggers). The embedding
application can set the global function pointer
scheme_notify_multithread to a function that takes an integer
parameter and returns void. This function is be called with 1
when thread-checking becomes necessary, and then with 0 when thread
checking is no longer necessary. An embedding program can use this
information to prevent unnecessary scheme_check_threads polling.
The below code illustrates how MrEd formerly set up
scheme_check_threads polling using the wxWindows wxTimer
class. (Any regular event-loop-based callback is appropriate.) The
scheme_notify_multithread pointer is set to
MrEdInstallThreadTimer. (MrEd no longer work this way, however.)
class MrEdThreadTimer : public wxTimer |
{ |
public: |
void Notify(void); /* callback when timer expires */ |
}; |
|
static int threads_go; |
static MrEdThreadTimer *theThreadTimer; |
#define THREAD_WAIT_TIME 40 |
|
void MrEdThreadTimer::Notify() |
{ |
if (threads_go) |
Start(THREAD_WAIT_TIME, TRUE); |
|
scheme_check_threads(); |
} |
|
static void MrEdInstallThreadTimer(int on) |
{ |
if (!theThreadTimer) |
theThreadTimer = new MrEdThreadTimer; |
|
if (on) |
theThreadTimer->Start(THREAD_WAIT_TIME, TRUE); |
else |
theThreadTimer->Stop(); |
|
threads_go = on; |
if (on) |
do_this_time = 1; |
} |
An alternate architecture, which MrEd now uses, is to send the main
thread into a loop, which blocks until an event is ready to handle.
Scheme automatically takes care of running all threads, and it does so
efficiently because the main thread blocks on a file descriptor, as
explained in Blocking the Current Thread.
8.4.1 Callbacks for Blocked Threads
Scheme threads are sometimes blocked on file descriptors, such as an
input file or the X event socket. Blocked non-main threads do not
block the main thread, and therefore do not affect the event loop, so
scheme_check_threads is sufficient to implement this case
correctly. However, it is wasteful to poll these descriptors with
scheme_check_threads when nothing else is happening in the
application and when a lower-level poll on the file descriptors can be
installed. If the global function pointer
scheme_wakeup_on_input is set, then this case is handled more
efficiently by turning off thread checking and issuing a “wakeup”
request on the blocking file descriptors through
scheme_wakeup_on_input.
A scheme_wakeup_on_input procedure takes a pointer to an array
of three fd_sets (use MZ_FD_SET instead of FD_SET, etc.)
and returns void. The scheme_wakeup_on_input
function does not sleep immediately; it just
sets up callbacks on the specified file descriptors. When input is
ready on any of those file descriptors, the callbacks are removed and
scheme_wake_up is called.
For example, the X Windows version of MrEd formerly set
scheme_wakeup_on_input to this MrEdNeedWakeup:
static XtInputId *scheme_cb_ids = NULL; |
static int num_cbs; |
|
static void MrEdNeedWakeup(void *fds) |
{ |
int limit, count, i, p; |
fd_set *rd, *wr, *ex; |
|
rd = (fd_set *)fds; |
wr = ((fd_set *)fds) + 1; |
ex = ((fd_set *)fds) + 2; |
|
limit = getdtablesize(); |
|
/* See if we need to do any work, really: */ |
count = 0; |
for (i = 0; i < limit; i++) { |
if (MZ_FD_ISSET(i, rd)) |
count++; |
if (MZ_FD_ISSET(i, wr)) |
count++; |
if (MZ_FD_ISSET(i, ex)) |
count++; |
} |
|
if (!count) |
return; |
|
/* Remove old callbacks: */ |
if (scheme_cb_ids) |
for (i = 0; i < num_cbs; i++) |
notify_set_input_func((Notify_client)NULL, (Notify_func)NULL, |
scheme_cb_ids[i]); |
|
num_cbs = count; |
scheme_cb_ids = new int[num_cbs]; |
|
/* Install callbacks */ |
p = 0; |
for (i = 0; i < limit; i++) { |
if (MZ_FD_ISSET(i, rd)) |
scheme_cb_ids[p++] = XtAppAddInput(wxAPP_CONTEXT, i, |
(XtPointer *)XtInputReadMask, |
(XtInputCallbackProc)MrEdWakeUp, NULL); |
if (MZ_FD_ISSET(i, wr)) |
scheme_cb_ids[p++] = XtAppAddInput(wxAPP_CONTEXT, i, |
(XtPointer *)XtInputWriteMask, |
(XtInputCallbackProc)MrEdWakeUp, NULL); |
if (MZ_FD_ISSET(i, ex)) |
scheme_cb_ids[p++] = XtAppAddInput(wxAPP_CONTEXT, i, |
(XtPointer *)XtInputExceptMask, |
(XtInputCallbackProc)MrEdWakeUp, |
NULL); |
} |
} |
|
/* callback function when input/exception is detected: */ |
Bool MrEdWakeUp(XtPointer, int *, XtInputId *) |
{ |
int i; |
|
if (scheme_cb_ids) { |
/* Remove all callbacks: */ |
for (i = 0; i < num_cbs; i++) |
XtRemoveInput(scheme_cb_ids[i]); |
|
scheme_cb_ids = NULL; |
|
/* ``wake up'' */ |
scheme_wake_up(); |
} |
|
return FALSE; |
} |
8.5 Sleeping by Embedded Scheme
When all Scheme threads are blocked, Scheme must “sleep” for a
certain number of seconds or until external input appears on some file
descriptor. Generally, sleeping should block the main event loop of
the entire application. However, the way in which sleeping is
performed may depend on the embedding application. The global function
pointer scheme_sleep can be set by an embedding application to
implement a blocking sleep, although Scheme implements this function
for you.
A scheme_sleep function takes two arguments: a float and a
void*. The latter is really points to an array of three
“fd_set” records (one for read, one for write, and one for
exceptions); these records are described further below. If the
float argument is non-zero, then the scheme_sleep function
blocks for the specified number of seconds, at most. The
scheme_sleep function should block until there is input one of
the file descriptors specified in the “fd_set,” indefinitely
if the float argument is zero.
The second argument to scheme_sleep is conceptually an array of
three fd_set records, but always use scheme_get_fdset to
get anything other than the zeroth element of this array, and
manipulate each “fd_set” with MZ_FD_SET,
MZ_FD_CLR, etc. instead of FD_SET, FD_CLR, etc.
The following function mzsleep is an appropriate
scheme_sleep function for most any Unix or Windows application.
(This is approximately the built-in sleep used by Scheme.)
void mzsleep(float v, void *fds) |
{ |
if (v) { |
sleep(v); |
} else { |
int limit; |
fd_set *rd, *wr, *ex; |
|
# ifdef WIN32 |
limit = 0; |
# else |
limit = getdtablesize(); |
# endif |
|
rd = (fd_set *)fds; |
wr = (fd_set *)scheme_get_fdset(fds, 1); |
ex = (fd_set *)scheme_get_fdset(fds, 2); |
|
select(limit, rd, wr, ex, NULL); |
} |
} |
8.6 Thread Functions
Creates a new thread, just like
thread.
Scheme_Object* | scheme_thread_w_details | ( | Scheme_Object* thunk, | | | Scheme_Config* config, | | | Scheme_Thread_Cell_Table* cells, | | | Scheme_Custodian* cust, | | | int suspend_to_kill) |
|
Like
scheme_thread, except that the created thread belongs to
cust instead of the current custodian, it uses the given
config for its initial configuration, it uses
cells for
its thread-cell table, and if
suspend_to_kill is non-zero, then
the thread is merely suspended when it would otherwise be killed
(through either
kill-thread or
custodian-shutdown-all).
The config argument is typically obtained through
scheme_current_config or scheme_extend_config. A
config is immutable, so different threads can safely use the
same value. The cells argument should be obtained from
scheme_inherit_cells; it is mutable, and a particular cell table
should be used by only one thread.
Creates a new semaphore.
Posts to sema.
Waits on sema. If try is not 0, the wait can fail and 0 is
returned for failure, otherwise 1 is returned.
Allows the current thread to be swapped out in favor of other
threads. If sleep_time positive, then the current thread will
sleep for at least sleep_time seconds.
After calling this function, a program should almost always call
scheme_making_progress next. The exception is when
scheme_thread_block is called in a polling loop that performs no
work that affects the progress of other threads. In that case,
scheme_making_progress should be called immediately after
exiting the loop.
See also scheme_block_until, and see also the
SCHEME_USE_FUEL macro in Allowing Thread Switches.
Swaps out the current thread in favor of thread.
Sends a break signal to the given thread.
int | | scheme_block_until | ( | Scheme_Ready_Fun f, | | | | | Scheme_Needs_Wakeup_Fun fdf, | | | | | Scheme_Object* data, | | | | | float sleep) |
|
The Scheme_Ready_Fun and Scheme_Needs_Wakeup_Fun
types are defined as follows:
typedef int (*Scheme_Ready_Fun)(Scheme_Object *data); |
typedef void (*Scheme_Needs_Wakeup_Fun)(Scheme_Object *data, |
void *fds); |
Blocks the current thread until f with data returns a true
value. The f function is called periodically – at least once
per potential swap-in of the blocked thread – and it may be called
multiple times even after it returns a true value. If f
with data ever returns a true value, it must continue to return
a true value until scheme_block_until returns. The argument
to f is the same data as provided
to scheme_block_until, and data is ignored
otherwise. (The data argument is not actually required to be
a Scheme_Object* value, because it is only used by f
and fdf.)
If Scheme decides to sleep, then the fdf function is called to
sets bits in fds, conceptually an array of three
fd_sets: one or reading, one for writing, and one for
exceptions. Use scheme_get_fdset to get elements of this
array, and manipulate an “fd_set” with MZ_FD_SET
instead of FD_SET, etc. Under Windows, an “fd_set” can
also accommodate OS-level semaphores or other handles via
scheme_add_fd_handle.
The fdf argument can be NULL, which implies that the thread
becomes unblocked (i.e., ready changes its result to true) only
through Scheme actions, and never through external processes (e.g.,
through a socket or OS-level semaphore) – with the exception that
scheme_signal_received may be called to indicate an external
change.
If sleep is a positive number, then scheme_block_until
polls f at least every sleep seconds, but
scheme_block_until does not return until f returns a
true value. The call to scheme_block_until can return before
sleep seconds if f returns a true value.
The return value from scheme_block_until is the return value
of its most recent call to f, which enables f to return
some information to the scheme_block_until caller.
See Blocking the Current Thread for information about restrictions on the
f and fdf functions.
int | | scheme_block_until_unless | ( | Scheme_Ready_Fun f, | | | | | Scheme_Needs_Wakeup_Fun fdf, | | | | | Scheme_Object* data, | | | | | float sleep, | | | | | Scheme_Object* unless_evt, | | | | | int break_on) |
|
Indicates that an external event may have caused the result of a
synchronization poll to have a different result. Unlike most other
Scheme functions, this one can be called from any OS-level thread, and
it wakes up if the Scheme thread if it is sleeping.
This function is called by the embedding program
when there is input on an external file descriptor. See
Sleeping by Embedded Scheme for more information.
Adds an OS-level semaphore (Windows) or other waitable handle
(Windows) to the “fd_set” fds. When Scheme performs
a “select” to sleep on fds, it also waits on the given
semaphore or handle. This feature makes it possible for Scheme to
sleep until it is awakened by an external process.
Scheme does not attempt to deallocate the given semaphore or handle,
and the “select” call using fds may be unblocked due to
some other file descriptor or handle in fds. If repost is
a true value, then h must be an OS-level semaphore, and if the
“select” unblocks due to a post on h, then h is
reposted; this allows clients to treat fds-installed semaphores
uniformly, whether or not a post on the semaphore was consumed by
“select”.
The scheme_add_fd_handle function is useful for implementing
the second procedure passed to scheme_wait_until, or for
implementing a custom input port.
Under Unix and Mac OS X, this function has no effect.
Adds an OS-level event type (Windows) to the set of types in the
“fd_set” fds. When Scheme performs a
“select” to sleep on fds, it also waits on events of
them specified type. This feature makes it possible for Scheme to
sleep until it is awakened by an external process.
The event mask is only used when some handle is installed with
scheme_add_fd_handle. This awkward restriction may force you
to create a dummy semaphore that is never posted.
Under Unix, and Mac OS X, this function has no effect.
void | | scheme_add_evt | ( | Scheme_Type type, | | | | | Scheme_Ready_Fun ready, | | | | | Scheme_Needs_Wakeup_Fun wakeup, | | | | | Scheme_Wait_Filter_Fun filter, | | | | | int can_redirect) |
|
The argument types are defined as follows:
typedef int (*Scheme_Ready_Fun)(Scheme_Object *data); |
typedef void (*Scheme_Needs_Wakeup_Fun)(Scheme_Object *data, |
void *fds); |
typedef int (*Scheme_Wait_Filter_Fun)(Scheme_Object *data); |
Extends the set of waitable objects for sync
to those with the type tag type. If filter is
non-NULL, it constrains the new waitable set to those objects
for which filter returns a non-zero value.
The ready and wakeup functions are used in the same way
was the arguments to scheme_block_until.
The can_redirect argument should be 0.
Like
scheme_add_evt, but for objects where waiting is based
on a semaphore. Instead of
ready and
wakeup functions,
the
getsema function extracts a semaphore for a given object:
typedef |
Scheme_Object *(*Scheme_Wait_Sema_Fun)(Scheme_Object *data, |
int *repost); |
If a successful wait should leave the semaphore waited, then
getsema should set *repost to 0. Otherwise, the
given semaphore will be re-posted after a successful wait. A
getsema function should almost always set *repost to
1.
Notifies the scheduler that the current thread is not simply calling
scheme_thread_block in a loop, but that it is actually
making progress.
Retrieves a thread-specific value installed with
scheme_tls_set.
If no thread-specific value is available for the given index,
NULL is
returned.
Calls
prim with the given
argc and
argv with breaks
enabled. The
prim function can block, in which case it might be
interrupted by a break. The
prim function should not block,
yield, or check for breaks after it succeeds, where “succeeds”
depends on the operation. For example,
tcp-accept/enable-break is implemented by wrapping this
function around the implementation of
tcp-accept; the
tcp-accept implementation does not block or yield after it
accepts a connection.
Scheme_Object* | scheme_make_thread_cell | ( | Scheme_Object* def_val, | | | int preserved, | | | Scheme_Object* cell, | | | Scheme_Thread_Cell_Table* cells, | | | Scheme_Object* cell, | | | Scheme_Thread_Cell_Table* cells, | | | Scheme_Object* v) |
|
Ends an atomic region with respect to Scheme threads. The current
thread may be swapped out immediately (i.e., the call to
scheme_end_atomic is assumed to be a safe point for thread
swaps).
Ends an atomic region with respect to Scheme threads, and also
prevents an immediate thread swap. (In other words, no Scheme
thread swaps will occur until a future safe point.)
Registers a callback to be invoked just after a Scheme thread is
swapped in. The data is provided back to f when it is
called, where Closure_Func is defined as follows:
typedef Scheme_Object *(*Scheme_Closure_Func)(Scheme_Object *); |