gauche.threads
- Threads ¶Gauche can use threads built on top of either POSIX threads (pthreads) or Windows threads, depending on the platform.
Provides thread API. The thread support is built-in, and this module merely provides a separate namespace.
Although you hardly need to distinguish
the underlying thread subsystem (either pthreads or Windows threads)
in Scheme level, you may want to detect the difference if your code
depends on a particular subsystem-specific semantics.
There are feature identifiers gauche.sys.pthreads
or gauche.sys.wthreads
, for pthreads platform and Windows thread
platform, respectively. You can use cond-expand
to switch the platform-dependent code
(see Feature conditional).
To check if threads are available at runtime, instead of compile time, use the following procedure.
{gauche.threads
}
Returns a symbol that indicates the supported thread type.
It returns pthread
for POSIX pthreads,
and wthread
for Windows threads.
(Note: On pthreads platforms, it should return pthreads
instead
of pthread
; then the returned symbol would correspond to the
value given to --enable-threads
option at configuration time.
It’s a historical overlook, stuck for the backward compatibility.)
Scheme-level thread API conforms SRFI-18, "Multithreading support", wrapped around Gauche’s object interface.
• Thread programming tips: | ||
• Thread procedures: | ||
• Thread local storage: | ||
• Synchronization primitives: | ||
• Thread exceptions: |
Although the surface API of threads looks simple and portable, you need to know how the threads are implemented in order to utilize the feature’s potential. Some languages support threads as language’s built-in construct and encourage programmers to express the calculation in terms of threads. However, it should be noted that in many cases there are alternative ways than threads to implement the desired algorithm, and you need to compare advantages and disadvantages of using threads depending on how the threads are realized in the underlying system.
In Gauche, the primary purpose of threads is to write programs that require preemptive scheduling, therefore are difficult to express in other ways. Preemptive threads may be required, for example, when you have to call a module that does blocking I/O which you can’t intercept, or may spend nondeterministic amount of calculation time that you want to interrupt.
For each Gauche’s thread, an individual VM is allocated and it is run by the dedicated POSIX thread. Thus the cost of context switch is the same as the native thread, but the creation of threads costs much higher than, say, lightweight threads built on top of call/cc. So Gauche’s preemptive threads are not designed for applications that want to create thousands of threads for fine-grained calculation.
The recommended usage is the technique so called "thread pool",
that you create a set of threads and keep them around for
long time and dispatch jobs to them as needed. Gauche provides
a thread pool implementation in control.thread-pool
module
(see control.thread-pool
- Thread pools).
Preemptive threads have other difficulties, and sometimes the alternatives may be a better fit than the native preemptive threads (e.g. see https://www-sop.inria.fr/mimosa/rp/FairThreads/html/FairThreads.html).
call/cc
. Creating call/cc-based threads
is much faster than creating native threads.
select
-based dispatching (See gauche.selector
- Simple dispatcher,
for example).
Of course, these technique are not mutually exclusive with native threads. You can use dispatcher with "thread pool" technique, for example. Just keep it in your mind that the native threads are not only but one of the ways to realize those features.
When you run a single-thread program that raises an unexpected (unhandled) error, Gauche prints an error message and a stack trace by default. So sometimes it perplexes programmers when a thread doesn’t print anything when it dies because of an unhandled error.
What’s happening is this: An unhandled error in a thread body would
cause the thread to terminate, and the error itself will propagate
to the thread who’s expecting the result of the terminated thread.
So, you get the error (wrapped by <uncaught-exception>
)
when you call thread-join!
on a thread which is terminated
because of an unhandled error. The behavior is defined in SRFI-18.
If you fire a thread for one-shot calculation, expecting to receive
the result by thread-join!
, then this is reasonable—you can
handle the error situation in the “parent” thread. However,
if you run a thread to loop indefinitely to process something and
not expect to retrieve its result via thread-join!
, this becomes
a pitfall; the thread may die unexpectedly but you wouldn’t know it.
(If such a thread is garbage-collected, a warning is printed.
However you wouldn’t know when that happens so you can’t count on it.)
For such threads, you should always wrap the body of such thread
with guard
, and handles the error explicitly. You can call
report-error
to display the default error message and a stack
trace.
(thread-start! (make-thread (^[] (guard (e [else (report-error e) #f]) ... thread body ...))))
See Thread exceptions, for the details of thread exception handling.
Note: As of 0.9.5, Gauche has a known bug that the tail call of
error handling clauses of guard
doesn’t become a proper
tail call. So, the following code, which should run safely
in Scheme, could eat up a stack:
(thread-start! (make-thread (^[] (let loop () (guard (e [else (report-error e) (loop)]) ... thread body ...)))))
For the time being, you can lift the call to loop outside of
guard
as workaround.
(thread-start! (make-thread (^[] (let loop () (guard (e [else (report-error e)]) ... thread body ...) (loop)))))
A thread. Each thread has an associated thunk which is evaluated by
a POSIX thread. When thunk returns normally, the result is stored
in the internal ’result’ slot, and can be retrieved by thread-join!
.
When thunk terminates abnormally, either by raising an exception or
terminated by thread-terminate!
, the exception condition is
stored in their internal ’result exception’ slot, and will be passed
to the thread calling thread-join!
on the terminated thread.
Each thread has its own dynamic environment and dynamic handler stack. When a thread is created, its dynamic environment is initialized by the creator’s dynamic environment. The thread’s dynamic handler stack is initially empty.
A thread is in one of the following four states at a time.
You can query the thread state by the thread-state
procedure.
new
A thread hasn’t started yet. A thread returned from make-thread
is in this state.
Once a thread is started it will never be in this state again.
At this point, no POSIX thread has been created; thread-start!
creates a POSIX thread to run the Gauche thread.
runnable
When a thread is started by thread-start!
, it becomes to this
state. Note that a thread blocked by a system call is still in
runnable
state.
stopped
A thread becomes in this state when it is stopped by thread-stop!
.
A thread in this state can go back to runnable
state by
thread-cont!
, resuming execution from the point when
it is stopped.
terminated
When the thread finished executing associated code, or
is terminated by thread-terminate!
, it becomes in this state.
Once a thread is in this state, the state can no longer be changed.
Access to the resources shared by multiple threads must be protected explicitly by synchronization primitives. See Synchronization primitives.
Access to ports are serialized by Gauche. If multiple threads attempt
to write to a port, their output may be interleaved but no output
will be lost, and the state of the port is kept consistent.
If multiple threads attempt to read from a port, a single read
primitive (e.g. read
, read-char
or read-line
)
works atomically.
Signal handlers are shared by all threads, but each thread has its own signal mask. See Signals and threads, for details.
A thread object has the following external slots.
<thread>
: name ¶A name can be associated to a thread.
This is just for the convenience of the application.
The primordial thread has the name "root
".
<thread>
: specific ¶A thread-local slot for use of the application.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns the current thread.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns #t
if obj is a thread, #f
otherwise.
[SRFI-18], [SRFI-21]{gauche.threads
}
Creates and returns a new thread to execute thunk.
To run the thread, you need to call thread-start!
.
The result of thunk may be retrieved by calling thread-join!.
You can provide the name of the thread by the optional argument name.
The created thread inherits the signal mask of the calling thread (see Signals and threads), and has a copy of parameters of the calling thread at the time of creation (see Parameters).
Other than those initial setups, there will be no relationship between
the new thread and the calling thread; there’s no parent-child
relationship like Unix process. Any thread can call thread-join!
on any other thread to receive the result. If nobody issues
thread-join!
and nobody holds a
reference to the created thread, it will be garbage collected
after the execution of the thread terminates.
If a thread execution is terminated because of uncaught exception,
and its result is never retrieved by thread-join!
, a warning
will be printed to the standard error port notifying
“thread dies a lonely death”: It usually indicates some coding
error. If you don’t collect the result of threads, you have to
make sure that all the exceptions are captured and handled within thunk.
Internally, this procedure just allocates and initializes a Scheme
thread object; the POSIX thread is not created until thread-start!
is called.
{gauche.threads
}
Returns one of symbols new
, runnable
, stopped
or terminated
, indicating the state of thread.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns the value of name slot of thread.
[SRFI-18], [SRFI-21]{gauche.threads
}
Gets/sets the value of the thread’s specific slot.
[SRFI-18], [SRFI-21]{gauche.threads
}
Starts the thread. An error is thrown if thread is not in
“new” state.
Returns thread.
{gauche.threads
}
Starts and returns the thread if it is in “new” state.
Otherwise, returns #f
.
Note that a thread can become a “terminated” state even if it is never
started, if another thread calls thread-terminate!
on it.
If that’s the possibility, this procedure comes handy,
for thread-start!
may raise an error if the thread is
termianted before started.
[SRFI-18], [SRFI-21]{gauche.threads
}
Waits termination of thread, or until the timeout is reached
if timeout is given.
Timeout must be either a <time>
object (see Time)
that specifies absolute point of time, or a real number that specifies
relative point of time from the time this procedure is called
in number of seconds, or #f
that indicates no timeout (default).
If thread terminates normally, thread-join!
returns
a value which is stored in the result field of thread.
If thread terminates abnormally, thread-join!
raises
an exception which is stored in the result exception field of thread.
It can be either a <terminated-thread-exception>
or
<uncaught-exception>
.
If the timeout is reached, thread-join! returns timeout-val
if given, or raises <join-timeout-exception>
.
See Thread exceptions, for the details of these exceptions.
[SRFI-18], [SRFI-21]{gauche.threads
}
Suspends the execution of the calling thread and yields CPU to other
waiting runnable threads, if any.
[SRFI-18], [SRFI-21]{gauche.threads
}
Suspends the calling thread for the period specified by timeout,
which must be either a <time>
object (see Time) that
specifies absolute point of time, or a real number that specifies
relative point of time from the time this procedure is called
in number of seconds.
After the specified time passes, thread-sleep!
returns with
unspecified value.
If timeout points a past time, thread-sleep!
returns
immediately.
{gauche.threads
}
Stops execution of the target thread temporarily.
You can resume the execution of the thread by thread-cont!
.
The stop request is handled synchronously; that is,
Gauche VM only checks the request at the “safe” point
of the VM and stops itself. It means if the thread is
blocked by a system call, it won’t become stopped
state
until the system call returns.
By default, thread-stop!
returns after the target
thread stops. Since it may take indefinitely, you can give optional
timeout argument to specify timeout. The timeout
argument can be #f
, which means no timeout, or
a <time>
object that specifies an absolute point of time,
or a real number specifying the number of seconds to wait.
The return value of thread-stop!
is thread if
it could successfully stop the target, or timeout-val
if timeout reached. When timeout-val is omitted, #f
is assumed.
If the target thread has already been stopped by the caller thread, this procedure returns thread immediately.
When thread-stop! is timed out, the request remains
effective even after thread-stop!
returns.
That is, the target thread may stop at some point in future.
The caller thread is expected to call thread-stop!
again to complete the stop operation.
An error is signaled if the target thread has already been
stopped by another thread (including the “pending” stop
request issued by other threads), or the target thread
is in neither runnable
nor stopped
state.
{gauche.threads
}
Resumes execution of thread which has been stopped by
thread-stop!
. An error is raised if thread
is not in stopped state, or it is stopped by another thread.
If the caller thread has already requested to stop the target
thread but timed out, calling thread-cont!
cancels
the request.
[SRFI-18+], [SRFI-21+]{gauche.threads
}
Terminates the specified thread thread.
The thread is terminated and an instance of
<terminated-thread-exception>
is stored in the result exception
field of thread.
If thread is the same as the calling thread, this procedure won’t return. Otherwise, this procedure returns unspecified value.
If thread is already terminated, this procedure does nothing.
By default, this procedure tries to termiante the thread in “safe”
point, so that the thread can keep the process in consistent state.
The locked mutexes, however, may remain locked; they become
“abandoned” state and when other thread tries to touch the mutex
it will raise <abandoned-mutex-exception>
.
This strategy may, however, leave the system’s thread unterminated, if the thread is blocking in certain system calls, even in Scheme level the thread is marked terminated. The system thread will terminate itself as soon as it resumes and return the control to Gauche runtime.
If, for some reason, you need to guarantee to terminate the system thread,
you can pass a true value to force
keyword argument. If you do so,
thread-terminate!
will forcibly terminate the system thread
after the usual graceful termination fails. It calls
pthread_cancel
or TerminateThread
as the last resort,
which can leave the process state inconsistent, so this mode should be
used in an absolute emergency.
[SRFI-226]{gauche.threads
}
Triggers termination of a thread thread, but returns immediately
without confirming the termination.
This procedure does not attempt “forcible” termination as the force
argument of thread-terminate!
does. It just initiate the first
stage of termination, which just send an internal request to thread.
The thread will eventually terminate but there’s no guarantee when it happens.
You can wait for thread to terminate later, using thread-join!
.
A thread local is an object that can hold per-thread value. It is defined in SRFI-226.
Note: Parameters used to be the way to keep per-thread value, but we’ve decided to split thread local storage from dyncamic binding to conform SRFI-226. Parameters should be used for dynamic binding, and thread locals should be used for per-thread value. If your existing code uses parameters for the per-thread value, update it to use thread locals. See Transition to SRFI-226 parameters, for the necessary changes.
[SRFI-226]{gauche.threads
}
Returns a new thread local, with init-value as an initial value.
The optional name argument specifies thread local’s name;
it is only used for debugging purpose.
A thread local is a handle to thread specific storage that can contain
single Scheme value. You can retrieve its value with tlref
and set a new value with tlset!
.
Once a thread local is created, it can be used from all existing
and future threads.
If #f
is specified in inheritable? (default),
when a thread local is tlref
-ed
first time in a thread, it returns init-value
.
If a true value is specified in inheritable?,
on the other hand, the initial value of the thread local
inherits the value of the creator thread at the moment
the thread is created.
[SRFI-226]{gauche.threads
}
Returns #t
if obj is a thread local, #f
otherwise.
[SRFI-226]{gauche.threads
}
Returns the value of a thread local tl.
[SRFI-226]{gauche.threads
}
Modifies the value of a thread local tl with val.
As Gauche’s extension, you can also use set!
with tlref
:
(set! (tlref tl) obj) ≡ (tlset! tl obj)
Mutexes and condition variables are the low-level synchronization devices. Defined in SRFI-18 and SRFI-21, they are portable across Scheme implementations that supports one of those srfis. (See Mutex, and see Condition variable, for the details.)
However, in most cases you want to use following higher-level synchronization utilities:
An atom is a wrapper of arbitrary Scheme object, and allows synchronized
access to it somewhat like Java’s synchronized
blocks.
A traditional synchronization primitive to share fixed number of resources.
Holds thread(s) until a set of operations is completed by other thread(s).
Wait until the preset number of thread reaches at the point.
Thread-safe queues (<mtqueue>
) are provided in data.queue
module
(see data.queue
- Queue), which works as a synchronized channel and
suitable to implement producer-consumer pattern.
• Mutex: | ||
• Condition variable: | ||
• Atom: | ||
• Semaphore: | ||
• Latch: | ||
• Barrier: |
{gauche.threads
}
A primitive synchronization device. It can take one of four states:
locked/owned, locked/not-owned, unlocked/abandoned and unlocked/not-abandoned.
A mutex can be locked (by mutex-lock!
) only if it is in unlocked state.
An ’owned’ mutex keeps a thread that owns it.
Typically an owner thread is the one that locked the mutex,
but you can make a thread other than the locking thread own a mutex.
A mutex becomes unlocked either by mutex-unlock!
or the owner
thread terminates. In the former case, a mutex becomes unlocked/not-abandoned
state. In the latter case, a mutex becomes unlocked/abandoned state.
A mutex has the following external slots.
<mutex>
: name ¶The name of the mutex.
<mutex>
: state ¶The state of the mutex. This is a read-only slot.
See the description of mutex-state
below.
<mutex>
: specific ¶A slot an application can keep arbitrary data. For example, an application can implement a ’recursive’ mutex using the specific field.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns #t
if obj is a mutex, #f
otherwise.
[SRFI-18], [SRFI-21]{gauche.threads
}
Creates and returns a new mutex object.
When created, the mutex is in unlocked/not-abandoned state.
Optionally, you can give a name to the mutex.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns the name of the mutex.
[SRFI-18], [SRFI-21]{gauche.threads
}
Gets/sets the specific value of the mutex.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns the state of mutex, which may be one of the followings:
The mutex is locked/owned, and the owner is the returned thread.
not-owned
The mutex is locked/not-owned.
abandoned
The mutex is unlocked/abandoned.
not-abandoned
The mutex is unlocked/not-abandoned.
[SRFI-18], [SRFI-21]{gauche.threads
}
Locks mutex. If mutex is in unlocked/not-abandoned
state, this procedure changes its state to locked state exclusively.
By default, mutex becomes locked/owned state, owned by the
calling thread. You can give other owner thread as thread argument.
If thread argument is given and #f
, the mutex becomes
locked/not-owned state.
If mutex is in unlocked/abandoned state, that is, some other thread has been terminated without unlocking it, this procedure signals ’abandoned mutex exception’ (see Thread exceptions) after changing the state of mutex.
If mutex is in locked state and
timeout is omitted or #f
, this procedure blocks until
mutex becomes unlocked. If timeout is specified,
mutex-lock!
returns when the specified time reaches in
case it couldn’t obtain a lock. You can give timeout
an absolute point of time (by <time>
object, see Time),
or a relative time (by a real number).
Mutex-lock!
returns #t
if mutex is successfully
locked, or #f
if timeout reached.
Note that mutex itself doesn’t implements a ’recursive lock’ feature; that is, if a thread that has locked mutex tries to lock mutex again, the thread blocks. It is not difficult, however, to implement a recursive lock semantics on top of this mutex. The following example is taken from SRFI-18 document:
(define (mutex-lock-recursively! mutex) (if (eq? (mutex-state mutex) (current-thread)) (let ((n (mutex-specific mutex))) (mutex-specific-set! mutex (+ n 1))) (begin (mutex-lock! mutex) (mutex-specific-set! mutex 0)))) (define (mutex-unlock-recursively! mutex) (let ((n (mutex-specific mutex))) (if (= n 0) (mutex-unlock! mutex) (mutex-specific-set! mutex (- n 1)))))
[SRFI-18], [SRFI-21]{gauche.threads
}
Unlocks mutex. The state of mutex becomes unlocked/not-abandoned.
It is allowed to unlock a mutex that is not owned by the calling thread.
If optional condition-variable is given, mutex-unlock!
serves the "condition variable wait" operation (e.g. pthread_cond_wait
in POSIX threads). The current thread atomically wait on
condition-variable and unlocks mutex.
The thread will be unblocked when other thread signals on
condition-variable (see condition-variable-signal!
and condition-variable-broadcast!
below), or timeout
reaches if it is supplied. The timeout argument can be either
a <time>
object to represent an absolute time point (see Time),
a real number to represent a relative time in seconds, or #f
which
means never. The calling thread may be unblocked prematurely,
so it should reacquire the lock of mutex and checks the
condition, as in the following example (it is taken from SRFI-18 document):
(let loop () (mutex-lock! m) (if (condition-is-true?) (begin (do-something-when-condition-is-true) (mutex-unlock! m)) (begin (mutex-unlock! m cv) (loop))))
The return value of mutex-unlock!
is #f
when it returns
because of timeout, and #t
otherwise.
{gauche.threads
}
Returns (lambda () (mutex-lock! mutex))
and
(lambda () (mutex-unlock! mutex))
, respectively.
Each closure is created at most once per mutex,
thus it is lighter than using literal lambda forms in a tight loop.
{gauche.threads
}
Calls thunk with locking a mutex mutex. This is defined as
follows.
(define (with-locking-mutex mutex thunk) (dynamic-wind (mutex-locker mutex) thunk (mutex-unlocker mutex)))
{gauche.threads
}
When this form is first evaluated, it evaluates expr …
returns the result(s) of the lat expression.
The results are memoized, so the subsequent
evaluation of the form returns the same results without
evaluating exprs.
It may sound somewhat similar to force
a promise,
but run-once
guarantees
that only one thread ever evaluates exprs. If more than one thread
tries to evaluate the same run-once
form, the one that aquire the
lock first evaluates it, making other threads wait. Note that force
does not prevent more than one thread to start evaluating the promise’s
body simultaneously; it
only guarantees that the first delivered result becomes the value
of the promise.
If the control goes out from this form before completing evaluation,
e.g. by an error, it is unlocked and another thread may start
evaluating exprs. If you have operations that should never
be evaluated more than once no matter what, you should use guard
or unwind-protect
to make sure
{gauche.threads
}
A condition variable keeps a set of threads that are waiting for
a certain condition to be true. When a thread modifies the state
of the concerned condition, it can call condition-variable-signal!
or condition-variable-broadcast!
, which unblock one or more
waiting threads so that they can check if the condition is satisfied.
A condition variable object has the following slots.
<condition-variable>
: name ¶The name of the condition variable.
<condition-variable>
: specific ¶A slot an application can keep arbitrary data.
Note that SRFI-18 doesn’t have a routine equivalent to pthreads’
pthread_cont_wait
. If you want to wait on condition variable,
you can pass a condition variable to mutex-unlock!
as an
optional argument (see above), then acquire mutex again by
mutex-lock!
. This design is for flexibility; see
SRFI-18 document for the details.
This is the common usage of pthreads’ condition variable:
while (some_condition != TRUE) { pthread_cond_wait(condition_variable, mutex); }
And it can be translated to SRFI-18 as follows:
(let loop () (unless some-condition (mutex-unlock! mutex condition-variable) (mutex-lock! mutex) (loop)))
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns #t
if obj is a condition variable,
#f
otherwise.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns a new condition variable. You can give its name by
optional name argument.
[SRFI-18], [SRFI-21]{gauche.threads
}
Returns the name of the condition variable.
[SRFI-18]、[SRFI-21]{gauche.threads
}
Gets/sets the specific value of the condition variable.
[SRFI-18]、[SRFI-21]{gauche.threads
}
If there are threads waiting on cv, causes the scheduler to select
one of them and to make it runnable.
[SRFI-18]、[SRFI-21]{gauche.threads
}
Unblocks all the threads waiting on cv.
An atom is a convenient wrapper to make operations on a given set of objects thread-safe. Instead of defining thread-safe counterparts of every structure, you can easily wrap an existing data structures to make it thread-safe.
{gauche.threads
}
Creates and returns an atom object with val … as the
initial values.
{gauche.threads
}
Returns #t
iff obj is an atom.
The following procedures can be used to atomically
access and update the content of an atom. They commonly
take optional timeout and timeout-val arguments,
both are defaulted to #f
. In some cases, the procedure
takes more than one timeout-val arguments.
With the default value #f
as timeout argument,
those procedures blocks until they acquire a lock.
The timeout arguments can be used to modify the behavior when
the lock cannot be acquired in timely manner.
timeout may be a <time>
object (see Time)
to specify an absolute point of time, or a real number
to specify the relative time in seconds. If timeout is
expired, those procedures give up acquiring the lock,
and the value given to timeout-val is returned.
In atomic
and atomic-update!
, you can make them return
multiple timeout values, by giving
more than one timeout-val arguments.
{gauche.threads
}
Returns index-th value of atom.
See above for timeout and timeout-val arguments.
(define a (atom 'a 'b)) (atom-ref a 0) ⇒ a (atom-ref a 1) ⇒ b
{gauche.threads
}
Calls proc with the current values in atom,
while locking atom. proc must take
as many arguments as the number of values atom has.
The returned value(s) of proc is the result of
atomic
, unless timeout occurs.
See above for timeout and timeout-val arguments.
For example, the ref/count
procedure
in the following example counts the number of times
the hashtable is referenced in thread-safe way.
(define a (atom (make-hash-table 'eq?) (list 0))) (define (ref/count a key) (atomic a (lambda (ht count-cell) (inc! (car count-cell)) (hash-table-get h key))))
{gauche.threads
}
Calls proc with the current values in atom
while locking atom, and updates the values in atom
by the returned values from proc.
proc must take as many arguments as the number of
values atom has, and must return the at least the same number of
values. If proc returns more than the number of values atom
holds, the extra values are not used to update the atom, but
is included in the return values of atomic-update!
.
The returned value(s) of atomic-update!
is what
proc returns, unless timeout occurs.
See above for timeout and timeout-val arguments.
The following example shows a thread-safe counter.
(define a (atom 0)) (atomic-update! a (cut + 1 <>))
Note: The term atom in historical Lisps meant an object that is not a cons cell (pair). Back then cons cells were the only aggregate datatype and there were few other datatypes (numbers and symbols), so having a complementary term to cells made sense.
Although it still appears in introductory Lisp tutorials, modern Lisps, including Scheme, has so many datatypes and it makes little sense to have a specific term for non-pair types.
Clojure adopted the term atom for thread-safe (atomic) primitive data, and we followed it.
Note: The constructor of atom is not make-atom
but atom
, following the convention of list
/make-list
,
vector
/make-vector
, and string
/make-string
;
that is, the name without make-
takes its elements as
variable number of arguments.
{gauche.threads
}
A semaphore manages “tokens” for a fixed number of resources.
A thread that wants to use one of the resources requests a token
by semaphore-acquire!
, and returns the token to the pool
by semaphore-release!
. When the thread requests a token
but none is available, it waits until a token becomes available
by some other thread returning one.
{gauche.threads
}
Creates and returns a new semaphore, with init-value tokens
available initially. If init-value is omitted, 0 is assumed.
Another optional argument name is an arbitrary Scheme object
and only used for a debugging purpose only; it is displayed
when a semaphore is printed.
{gauche.threads
}
Returns #t
iff obj is a semaphore.
{gauche.threads
}
Obtain a token from a semaphore sem.
If a token is available, this returns #t
immediately,
decrementing the available token count of sem atomically.
If no token is available, this waits until a token becomes
avaialble, or timeout reaches if it is given. If timeout
occurs, it returns timeout-val, defaulted to #f
.
The value of timeout is the same as mutex
; it can be
#f
(no timeout, default), a <time>
object to specify an absolute
point of time, or a real number to specify the minimum number of seconds
to wait.
{gauche.threads
}
Return a token to a semaphore sem, by incrementing its token count,
and running one of the threads waiting with semaphore-acquire!
atomically.
The count argument specifies number of tokens to return, defaulted to 1.
Actually, the “token” model is to make it easier to understand the
idea of semaphore, but internally it’s just a counter, so you can
call semaphore-release!
even you haven’t called semaphore-acuqire!
.
{gauche.threads
}
A latch is a synchornization device with a counter.
Any number of threads can wait until the counter becomes zero.
Once the counter reaches zero, all threads go.
(As opposed to semaphores, whose internal counter affects
number of waiting threads as well.)
One simple usage is to start with initial count 1; it is sometimes called a “gate”. You can let threads wait before the gate, and decrement the counter, then boom! All threads proceeds.
{gauche.threads
}
Creates and returns a new latch with the counter value initial-count,
which must be an exact positive integer.
The optional name argument can be any Scheme object, and used only for debugging. It is displayed when the latch object is printed.
{gauche.threads
}
Returns #t
iff obj is a latch.
{gauche.threads
}
Decrement the counter of latch by n.
If n is omitted, 1 is assumed.
If the value of the latch becomes 0 or less, threads waiting on the latch are woken up.
Returns the updated counter value.
N must be an exact integer. Zero or negative value is allowed.
{gauche.threads
}
If latch’s count was non-zero, make it zero and wakes up
the threads waiting on it.
If latch’s count has already been zero, do nothing.
Returns the previous count.
{gauche.threads
}
If the counter of latch is zero or negative, returns #t
immeiately. Otherwise, the calling thread is blocked until
the counter value becomes zero or below, or timeout reaches.
If the thread started by the counter value, #t
is returned.
If the thread started by timeout, timeout-val is returned,
whose default is #f
.
The timeout argument must be either #f
(no timeout, default),
a <time>
object (absolute point of time), or a real number
indicates number of seconds from the time this procedure is called.
{gauche.threads
}
A barrier is a synchronization primitive that multiple threads
wait by barrier-await
until a specified number of threads reach the point.
Once the specified number of threads get to the barrier,
all threads go, and the barrier automatically returns to the initial
state; the next thread that comes to the barrier waits again.
A barrier can have an optional action, which is run by the last thread that reached to the barrier before everyone goes.
A barrier can be in a “broken” state when any one of the threads
timeout, or the action raises an uncaptured exception. Once the barrier
is broken, all threads waiting on it is released as if timeout has
occured. The broken barrier remains broken (so any thread that reaches
it just passes through) until it is reset by barrier-reset!
.
{gauche.threads
}
Craetes and returns a new barrier. The threshold argument
must be an exact nonnegative integer to specify the number of threads
to let them go—threads calling barrier-await
waits until
the number of threads waiting reaches threshold.
The optional action argument must be either #f
(no action)
or a thunk. If it is a thunk, it is run by the last thread
that calls barrier-await
, before all the thread will go.
The optional name argument can be any Scheme object, and only used to print the barrier instance, to help debugging.
{gauche.threads
}
Returns #t
iff obj is a barrier.
{gauche.threads
}
Waits on barrier until the number of waiting threads reaches
the threshold of the barrier, or the barrier is broken.
A barrier can be broken if any of waiting threads hit the timeout, or an uncaptured exception is raised from the action thunk of the barrier.
The timeout and timeout-val are the same as other synchronization
primitives; timeout may be #f
(no timeout, default),
a <time>
object
to specify an absolute time point, or a real number indicating relative
time in number of seconds; timeout-val is the value returned
when barrier-await
returns prematurely—either timeout, or
other barrier breakage conditions.
The default of timeout-val is #f
.
It returns an integer if the thread is released by reaching the threshold,
or timeout-val if the barrier is broken. The integer indicates
the number of threads required to reach the threshold at the time
barrier-wait
is called; that is, if you’re the first to reach
the barrier it is threshold - 1, and if you’re the last it is 0.
If the barrier is already broken when you call barrier-await
,
it returns immediately with timeout-val.
If the threads are released normally, the barrier turns back to the
initial state so you can wait on it again. If the threads are released
because the barrier is broken, it remains in broken state until
reset by barrier-reset!
.
{gauche.threads
}
Returns #t
iff barrier is in broken state.
{gauche.threads
}
Resets the barrier to the initial state. If the barrire is broken,
the broken state is cleared. If any thread is waiting on the barrier,
it is released as if the number of threads reaches the threshold.
Some types of exceptions may be thrown from thread-related procedures. These exceptions can be handled by Gauche’s exception mechanism (see Exceptions).
These are originally defined in SRFI-18. SRFI-226 follows them, but uses slightly different names for the consistency with modern Scheme. We support both.
[SRFI-226]{gauche.threads
}
A base class of thread-related exceptions. Inherits <exception>
class.
It has one slot.
<thread-exception>
: thread ¶A thread that threw this exception.
The name &thread
is defined by SRFI-226. Note that SRFI-226
does not define thread
slot; it’s Gauche’s extension.
[SRFI-226]{gauche.threads
}
An exception thrown by thread-join!
when a timeout reaches
before the waited thread returns. Inherits <thread-exception>
.
The name &thread-timeout
is defined by SRFI-226.
[SRFI-226]{gauche.threads
}
An exception thrown by mutex-lock!
when a mutex to be
locked is in unlocked/abandoned state. Inherits <thread-exception>
.
It has one additional slot.
<abandoned-mutex-exception>
: mutex ¶A mutex that caused this exception.
The name &thread-abandoned-mutex
is defined by SRFI-226.
[SRFI-226]{gauche.threads
}
An exception thrown by thread-join!
when the waited thread
is terminated abnormally (by thread-terminate!
).
Inherits <thread-exception>
. It has one additional slot.
<terminated-thread-exception>
: terminator ¶A thread that terminated the thread that causes this exception.
The name &thread-already-terminated
is defined by SRFI-226.
SRFI-226 doesn’t define terminator
slot, though.
[SRFI-226]{gauche.threads
}
An exception thrown by thread-join!
when the waited thread
is terminated by an uncaught exception.
Inherits <thread-exception>
. It has one additional slot.
<uncaught-exception>
: reason ¶An exception that caused the termination of the thread.
The name &uncaught-exception
is defined by SRFI-226.
[SRFI-226] This is a condition thrown when the runtime detects non-thread-safe data is being modified by multiple threads concurrently. SRFI-226 defines this, but it allows implementations not to detect this situation if it’s costly. Currently, no Gauche APIs detect this condition, though it may do so in future. For now, this is provided for the compatibility with SRFI-226.
[SRFI-226]{gauche.threads
}
Creates a new <thread-exception>
condition. This is for SRFI-226
compatibility. Usually you don’t need to use this directly, for
the actual condition is of a subclass of <thread-exception>
.
The thread
slot is initialized with the calling thread.
[SRFI-226]{gauche.threads
}
Returns #t
if obj is a <thread-exception>
(&thread
) condition,
#f
otherwise.
[SRFI-226]{gauche.threads
}
Creates a new <join-timeout-exception>
(&thread-timeout
) condition.
[SRFI-18], [SRFI-21], [SRFI-226]{gauche.threads
}
Returns #t
if obj is a
<join-timeout-exception>
(&thread-timeout
) condition,
#f
otherwise.
The name join-timeout-exception?
is defined by SRFI-18/21,
and thread-timeout-condition?
is by SRFI-226. They are the same.
[SRFI-226]{gauche.threads
}
Creates a new <abandoned-mutex-exception>
(&thread-abandoned-mutex
) condition.
[SRFI-18], [SRFI-21], [SRFI-226]{gauche.threads
}
Returns #t
if obj is a
<abandoned-mutex-exception>
(), #f
otherwise.
The name abandoned-mutex-exception?
is defined by SRFI-18/21,
and thread-abandoned-mutex-condition?
is by SRFI-226. They are the same.
Creates a new <terminated-thread-exception>
(&thread-already-terminated
) condition.
[SRFI-18], [SRFI-21], [SRFI-226]{gauche.threads
}
Returns #t
if obj is an instance of
<terminated-thread-exception>
, #f
otherwise.
The name terminated-thread-exception?
is defined by SRFI-18/21,
and thread-already-terminated-condition?
is by SRFI-226. They are the same.
Creates a new <uncaught-exception>
(&uncaught-exception
) condition.
The argument is set to the reason
slot of the condition.
[SRFI-18], [SRFI-21], [SRFI-226]{gauche.threads
}
Returns #t
if obj is an instance of
<uncaught-exception>
, #f
otherwise.
The name uncaught-exception?
is defined by SRFI-18/21,
and uncaught-exception-condition?
is by SRFI-226. They are the same.
Returns the value of reason
slot of <uncaught-exception>
object.
The name uncaught-exception-reason
is defined in
SRFI-18/21, and uncaught-exception-condition-reason
is
defined in SRFI-226. Both works the same.
[SRFI-226]{gauche.threads
}
Creates a new <concurrent-modification-violation>
(&concurrent-modification
) condition.
[SRFI-226]{gauche.threads
}
Returns #t
if obj is a
<concurrent-modification-violation>
(&concurrent-modification
) condition, #f
otherwise.