9. Threads, Mutexes and Condition Variables

The thread system provides the following data types:

  • Thread (a virtual processor which shares object space with all other threads)

  • Mutex (a mutual exclusion device, also known as a lock and binary semaphore)

  • Condition variable (a set of blocked threads)

The STklos thread system is conform to SRFI-18 (Multithreading support), and implement all the SRFI mechanisms. See this SRFI documentation for a more complete description

9.1. Threads

STklos procedure

(make-thread thunk)
(make-thread thunk name)
(make-thread thunk name stack-size)

Returns a new thread. This thread is not automatically made runnable (the procedure thread-start! must be used for this). A thread has the following fields: name, specific, end-result, end-exception, and a list of locked/owned mutexes it owns. The thread’s execution consists of a call to thunk with the "initial continuation". This continuation causes the (then) current thread to store the result in its end-result field, abandon all mutexes it owns, and finally terminate. The dynamic-wind stack of the initial continuation is empty. The optional name is an arbitrary Scheme object which identifies the thread (useful for debugging); it defaults to an unspecified value. The specific field is set to an unspecified value. The thread inherits the dynamic environment from the current thread. Moreover, in this dynamic environment the exception handler is bound to the "initial exception handler" which is a unary procedure which causes the (then) current thread to store in its end-exception field an "uncaught exception" object whose "reason" is the argument of the handler, abandon all mutexes it owns, and finally terminate.

The optional parameter stack-size permits to specify the size (in words) reserved for the thread. This option does not exist in SRFI-18.

STklos procedure

(current-thread)

Returns the current thread.

(eq? (current-thread) (current-thread))  =>  #t

STklos procedure

(thread-start! thread)

Makes thread runnable. The thread must be a new thread. Thread-start! returns the thread.

(let ((t (thread-start! (make-thread
                           (lambda () (write 'a))))))
   (write 'b)
   (thread-join! t))       =>  unspecified
                               after writing ab or ba

STklos procedure

(thread-yield!)

The current thread exits the running state as if its quantum had expired. Thread-yield! returns an unspecified value.

STklos procedure

(thread-terminate! thread)

Causes an abnormal termination of the thread. If the thread is not already terminated, all mutexes owned by the thread become unlocked/abandoned and a "terminated thread exception" object is stored in the thread’s end-exception field. If thread is the current thread, thread-terminate! does not return. Otherwise, thread-terminate! returns an unspecified value; the termination of the thread will occur before thread-terminate! returns.

  • This operation must be used carefully because it terminates a thread abruptly and it is impossible for that thread to perform any kind of cleanup. This may be a problem if the thread is in the middle of a critical section where some structure has been put in an inconsistent state. However, another thread attempting to enter this critical section will raise an "abandoned mutex exception" because the mutex is unlocked/abandoned.

  • On Android, thread-terminate! can be used only to terminate the current thread. Trying to kill another thread produces an error.

STklos procedure

(thread-sleep! timeout)

The current thread waits until the timeout is reached. This blocks the thread only if timeout represents a point in the future. It is an error for timeout to be #f. Thread-sleep! returns an unspecified value.

STklos procedure

(thread-join! thread)
(thread-join! thread timeout)
(thread-join! thread timeout timeout-val)

The current thread waits until the thread terminates (normally or not) or until the timeout is reached if timeout is supplied. If the timeout is reached, thread-join! returns timeout-val if it is supplied, otherwise a "join timeout exception" is raised. If the thread terminated normally, the content of the end-result field is returned, otherwise the content of the end-exception field is raised.

(let ((t (thread-start! (make-thread (lambda ()
                                       (expt 2 100))))))
  (thread-sleep! 1)
  (thread-join! t)) => 1267650600228229401496703205376

STklos procedure

(thread? obj)

Returns #t if obj is a thread, otherwise returns #f.

(thread? (current-thread))  => #t
(thread? 'foo)              => #f

STklos procedure

(thread-name thread)

Returns the name of the thread.

(thread-name (make-thread (lambda () #f) 'foo))  =>  foo

STklos procedure

(thread-stack-size thread)

Returns the allocated stack size for thread.

(thread-stack-size (make-thread (lambda () #f) 'foo 2000)) => 2000

Note that this procedure is not present in SRFI-18.

STklos procedure

(thread-specific thread)

Returns the content of the `thread’s specific field.

STklos procedure

(thread-specific-set! thread)

Stores obj into the thread’s specific field. `Thread-specific-set! returns an unspecified value.

(thread-specific-set! (current-thread) "hello")
           =>  unspecified
(thread-specific (current-thread))
           =>  "hello"

9.2. Mutexes

STklos procedure

(make-mutex)
(make-mutex name)

Returns a new mutex in the unlocked/not-abandoned state. The optional name is an arbitrary Scheme object which identifies the mutex (useful for debugging); it defaults to an unspecified value. The mutex’s specific field is set to an unspecified value.

STklos procedure

(mutex? obj)

Returns #t if obj is a mutex, otherwise returns #f.

STklos procedure

(mutex-name mutex)

Returns the name of the mutex.

(mutex-name (make-mutex 'foo))  =>  foo

STklos procedure

(mutex-specific mutex)

Returns the content of the `mutex’s specific field.

STklos procedure

(mutex-specific! mutex obj)

Stores obj into the `mutex’s specific field and eturns an unspecified value.

(define m (make-mutex))
(mutex-specific-set! m "hello")  =>  unspecified
(mutex-specific m)               =>  "hello"

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

STklos procedure

(mutex-state mutex)

Returns information about the state of the mutex. The possible results are:

  • a thread T: the mutex is in the locked/owned state and thread T is the owner of the mutex

  • the symbol not-owned: the mutex is in the locked/not-owned state

  • the symbol abandoned: the mutex is in the unlocked/abandoned state

  • the symbol not-abandoned: the mutex is in the unlocked/not-abandoned state

(mutex-state (make-mutex))  =>  not-abandoned

(define (thread-alive? thread)
  (let ((mutex (make-mutex)))
    (mutex-lock! mutex #f thread)
    (let ((state (mutex-state mutex)))
      (mutex-unlock! mutex) ; avoid space leak
      (eq? state thread))))

STklos procedure

(mutex-lock! mutex)
(mutex-lock! mutex timeout)
(mutex-lock! mutex timeout thread)

If the mutex is currently locked, the current thread waits until the mutex is unlocked, or until the timeout is reached if timeout is supplied. If the timeout is reached, mutex-lock! returns #f. Otherwise, the state of the mutex is changed as follows:

  • if thread is #f the mutex becomes locked/not-owned,

  • otherwise, let T be thread (or the current thread if thread is not supplied),

    • if T is terminated the mutex becomes unlocked/abandoned,

    • otherwise mutex becomes locked/owned with T as the owner.

After changing the state of the mutex, an "abandoned mutex exception" is raised if the mutex was unlocked/abandoned before the state change, otherwise mutex-lock! returns #t.

(define (sleep! timeout)
  ;; an alternate implementation of thread-sleep!
  (let ((m (make-mutex)))
  (mutex-lock! m #f #f)
  (mutex-lock! m timeout #f)))

STklos procedure

(mutex-unlock! mutex)
(mutex-unlock! mutex condition-variable)
(mutex-unlock! mutex condition-variable timeout)

Unlocks the mutex by making it unlocked/not-abandoned. It is not an error to unlock an unlocked mutex and a mutex that is owned by any thread. If condition-variable is supplied, the current thread is blocked and added to the condition-variable before unlocking mutex; the thread can unblock at any time but no later than when an appropriate call to condition-variable-signal! or condition-variable-broadcast! is performed (see below), and no later than the timeout (if timeout is supplied). If there are threads waiting to lock this mutex, the scheduler selects a thread, the mutex becomes locked/owned or locked/not-owned, and the thread is unblocked. mutex-unlock! returns #f when the timeout is reached, otherwise it returns #t.

STklos procedure

(with-mutex mtx <thunk>)

Executes thunk, protected by mutex mtx. The mutex will be locked before and released after execution of body, and also on entrance or departure of its dynamic context (lock and unlock are used within dynamic-wind).

9.3. Condition Variables

STklos procedure

(make-conditon-variable)
(make-conditon-variable name)

Returns a new empty condition variable. The optional name is an arbitrary Scheme object which identifies the condition variable (useful for debugging); it defaults to an unspecified value. The condition variable’s specific field is set to an unspecified value.

STklos procedure

(conditon-variable? obj)

Returns #t if obj is a condition variable, otherwise returns #f.

STklos procedure

(conditon-variable-name conditon-variable)

Returns the name of the condition-variable.

STklos procedure

(conditon-variable-specific conditon-variable)

Returns the content of the `condition-variable’s specific field.

STklos procedure

(conditon-variable-specific-set! conditon-variable obj)

Stores obj into the `condition-variable’s specific field.

STklos procedure

(condition-variable-signal! condition-variable)

If there are threads blocked on the condition-variable, the scheduler selects a thread and unblocks it. Condition-variable-signal! returns an unspecified value.

STklos procedure

(condition-variable-broadcast! condition-variable)

Unblocks all the threads blocked on the condition-variable. Condition-variable-broadcast! returns an unspecified value.

9.4. Conditions

STklos procedure

(join-timeout-exception? obj)

Returns #t if obj is a join timeout exception object, otherwise returns #f.

A join timeout exception is raised when thread-join! is called, the timeout is reached and no timeout-val is supplied.

STklos procedure

(abandoned-mutex-exception? obj)

Returns #t if obj is an abandoned mutex exception object, otherwise returns #f.

An abandoned mutex exception is raised when the current thread locks a mutex that was owned by a thread which terminated ,(see mutex-lock!).

STklos procedure

(terminated-thread-exception? obj)

Returns #t if obj is a terminated thread exception object, otherwise returns #f.

A terminated thread exception is raised when thread-join! is called and the target thread has terminated as a result of a call to thread-terminate!.

STklos procedure

(uncaught-exception? obj)

Returns #t if obj is an uncaught exception object, otherwise returns #f.

An uncaught exception is raised when thread-join! is called and the target thread has terminated because it raised an exception that called the initial exception handler of that thread.

STklos procedure

(uncaught-exception-reason exc)

Returns the object which was passed to the initial exception handler of that thread (exc must be an uncaught exception object).


1. Documentation about hygienic macros has been stolen in the SLIB manual
1. In fact define-module on a given name defines a new module only the first time it is invoked on this name. By this way, interactively reloading a module does not define a new entity, and the other modules which use it are not altered.
2. This transcript uses the default toplevel loop which displays the name of the current module in the evaluator prompt.
1. Under Unix, you can simply connect to a listening socket with the telnet of netcat command. For the given example, this can be achieved with netcat localhost 12345
2. Port 13, if open, can be used for testing: making a connection to it permits to know the distant system’s idea of the time of day.
1. The "pattern matching compiler" has been written by Jean-Marie Geffroy and is part of the Manuel Serrano’s Bigloo compiler since several years [Bigloo]
1. This section is an adaptation of Jeff Dalton’s (J.Dalton@ed.ac.uk) "Brief introduction to CLOS" which can be found at http://www.aiai.ed.ac.uk/~jeff/clos-guide.html