For Development HEAD DRAFTSearch (procedure/syntax/module):

9.35 gauche.threads - Threads

Gauche can use threads built on top of either POSIX threads (pthreads) or Windows threads, depending on the platform.

Module: gauche.threads

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.

Function: gauche-thread-type

{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.


9.35.1 Thread programming tips

What’s Gauche threads for

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).

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.

Uncaught errors in a thread body

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)))))

9.35.2 Thread procedures

Builtin Class: <thread>

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.

Instance Variable of <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".

Instance Variable of <thread>: specific

A thread-local slot for use of the application.

Function: current-thread

[SRFI-18], [SRFI-21]{gauche.threads} Returns the current thread.

Function: thread? obj

[SRFI-18], [SRFI-21]{gauche.threads} Returns #t if obj is a thread, #f otherwise.

Function: make-thread thunk :optional name

[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.

Function: thread-state thread

{gauche.threads} Returns one of symbols new, runnable, stopped or terminated, indicating the state of thread.

Function: thread-name thread

[SRFI-18], [SRFI-21]{gauche.threads} Returns the value of name slot of thread.

Function: thread-specific thread
Function: thread-specific-set! thread value

[SRFI-18], [SRFI-21]{gauche.threads} Gets/sets the value of the thread’s specific slot.

Function: thread-start! thread

[SRFI-18], [SRFI-21]{gauche.threads} Starts the thread. An error is thrown if thread is not in “new” state. Returns thread.

Function: thread-try-start! 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.

Function: thread-join! thread :optional timeout timeout-val

[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.

Function: thread-yield!

[SRFI-18], [SRFI-21]{gauche.threads} Suspends the execution of the calling thread and yields CPU to other waiting runnable threads, if any.

Function: thread-sleep! timeout

[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.

Function: thread-stop! thread :optional timeout timeout-val

{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.

Function: thread-cont! thread

{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.

Function: thread-terminate! thread :key force

[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.

Function: thread-schedule-terminate! thread

[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!.


9.35.3 Thread local storage

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.

Function: make-thread-local init-value :optional inheritable? name

[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.

Function: thread-local? obj

[SRFI-226]{gauche.threads} Returns #t if obj is a thread local, #f otherwise.

Function: tlref tl

[SRFI-226]{gauche.threads} Returns the value of a thread local tl.

Function: tlset! tl val

[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)

9.35.4 Synchronization primitives

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:

Atoms

An atom is a wrapper of arbitrary Scheme object, and allows synchronized access to it somewhat like Java’s synchronized blocks.

Semaphores

A traditional synchronization primitive to share fixed number of resources.

Latch

Holds thread(s) until a set of operations is completed by other thread(s).

Barrier

Wait until the preset number of thread reaches at the point.

MT-Queues

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.


9.35.4.1 Mutex

Builtin Class: <mutex>

{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.

Instance Variable of <mutex>: name

The name of the mutex.

Instance Variable of <mutex>: state

The state of the mutex. This is a read-only slot. See the description of mutex-state below.

Instance Variable of <mutex>: specific

A slot an application can keep arbitrary data. For example, an application can implement a ’recursive’ mutex using the specific field.

Function: mutex? obj

[SRFI-18], [SRFI-21]{gauche.threads} Returns #t if obj is a mutex, #f otherwise.

Function: make-mutex :optional name

[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.

Function: mutex-name mutex

[SRFI-18], [SRFI-21]{gauche.threads} Returns the name of the mutex.

Function: mutex-specific mutex
Function: mutex-specific-set! mutex value

[SRFI-18], [SRFI-21]{gauche.threads} Gets/sets the specific value of the mutex.

Function: mutex-state mutex

[SRFI-18], [SRFI-21]{gauche.threads} Returns the state of mutex, which may be one of the followings:

a thread

The mutex is locked/owned, and the owner is the returned thread.

symbol not-owned

The mutex is locked/not-owned.

symbol abandoned

The mutex is unlocked/abandoned.

symbol not-abandoned

The mutex is unlocked/not-abandoned.

Function: mutex-lock! mutex :optional timeout thread

[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)))))
Function: mutex-unlock! mutex :optional condition-variable timeout

[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.

Function: mutex-locker mutex
Function: mutex-unlocker mutex

{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.

Function: with-locking-mutex mutex thunk

{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)))
Macro: run-once expr …

{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


9.35.4.2 Condition variable

Builtin Class: <condition-variable>

{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.

Instance Variable of <condition-variable>: name

The name of the condition variable.

Instance Variable of <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)))
Function: condition-variable? obj

[SRFI-18], [SRFI-21]{gauche.threads} Returns #t if obj is a condition variable, #f otherwise.

Function: make-condition-variable :optional name

[SRFI-18], [SRFI-21]{gauche.threads} Returns a new condition variable. You can give its name by optional name argument.

Function: condition-variable-name cv

[SRFI-18], [SRFI-21]{gauche.threads} Returns the name of the condition variable.

Function: condition-variable-specific cv
Function: condition-variable-specific-set! cv value

[SRFI-18]、[SRFI-21]{gauche.threads} Gets/sets the specific value of the condition variable.

Function: condition-variable-signal! cv

[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.

Function: condition-variable-broadcast! cv

[SRFI-18]、[SRFI-21]{gauche.threads} Unblocks all the threads waiting on cv.


9.35.4.3 Atom

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.

Function: atom val …

{gauche.threads} Creates and returns an atom object with val … as the initial values.

Function: atom? obj

{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.

Function: atom-ref atom :optional index timeout timeout-val

{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
Function: atomic atom proc :optional timeout timeout-val timeout-val2 …

{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))))
Function: atomic-update! atom proc :optional timeout timeout-val timeout-val2 …

{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.


9.35.4.4 Semaphore

Builtin Class: <semaphore>

{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.

Function: make-semaphore :optional init-value name

{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.

Function: semaphore? obj

{gauche.threads} Returns #t iff obj is a semaphore.

Function: semaphore-acquire! sem :optional timeout timeout-val

{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.

Function: semaphore-release! sem :optional count

{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!.


9.35.4.5 Latch

Builtin Class: <latch>

{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.

Function: make-latch initial-count :optional name

{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.

Function: latch? obj

{gauche.threads} Returns #t iff obj is a latch.

Function: latch-dec! latch :optional n

{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.

Function: latch-clear! latch

{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.

Function: latch-await latch :optional timeout timeout-val

{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.


9.35.4.6 Barrier

Builtin Class: <barrier>

{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!.

Function: make-barrier threshold :optional action name

{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.

Function: barrier? obj

{gauche.threads} Returns #t iff obj is a barrier.

Function: barrier-await barrier :optional timeout timeout-val

{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!.

Function: barrier-broken? barrier

{gauche.threads} Returns #t iff barrier is in broken state.

Function: barrier-reset! barrier

{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.


9.35.5 Thread exceptions

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.

Builtin Class: <thread-exception>
Condition Type: &thread

[SRFI-226]{gauche.threads} A base class of thread-related exceptions. Inherits <exception> class. It has one slot.

Instance Variable of <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.

Builtin Class: <join-timeout-exception>
Condition Type: &thread-timeout

[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.

Builtin Class: <abandoned-mutex-exception>
Condition Type: &thread-abandoned-mutex

[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.

Instance Variable of <abandoned-mutex-exception>: mutex

A mutex that caused this exception.

The name &thread-abandoned-mutex is defined by SRFI-226.

Builtin Class: <terminated-thread-exception>
Condition Type: &thread-already-terminated

[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.

Instance Variable of <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.

Builtin Class: <uncaught-exception>
Condition Type: &uncaught-exception

[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.

Instance Variable of <uncaught-exception>: reason

An exception that caused the termination of the thread.

The name &uncaught-exception is defined by SRFI-226.

Builtin Class: <concurrent-modification-violation>
Condition Type: &concurrent-modification

[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.

Function: make-thread-condition

[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.

Function: thread-condition? obj

[SRFI-226]{gauche.threads} Returns #t if obj is a <thread-exception> (&thread) condition, #f otherwise.

Function: make-thread-timeout-condition

[SRFI-226]{gauche.threads} Creates a new <join-timeout-exception> (&thread-timeout) condition.

Function: join-timeout-exception? obj
Function: thread-timeout-condition? obj

[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.

Function: make-thread-abandoned-mutex

[SRFI-226]{gauche.threads} Creates a new <abandoned-mutex-exception> (&thread-abandoned-mutex) condition.

Function: abandoned-mutex-exception? obj
Function: thread-abandoned-mutex-condition? obj

[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.

Function: terminated-thread-exception? obj
Function: thread-already-terminated-condition? obj

[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.

Function: uncaught-exception? obj
Function: uncaught-exception-condition? obj

[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.

Function: make-concurrent-modification-violation

[SRFI-226]{gauche.threads} Creates a new <concurrent-modification-violation> (&concurrent-modification) condition.

Function: concurrent-modification-violation? obj

[SRFI-226]{gauche.threads} Returns #t if obj is a <concurrent-modification-violation> (&concurrent-modification) condition, #f otherwise.



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT