Skip to content

SRFI-18 Threads

Multithreading primitives for concurrent programming. Import with (import (srfi 18)). See also Kaappi Extensions for lightweight green threads (fibers).

Note

thread-start! requires the --experimental-threads flag. Without it, calling thread-start! raises an error. Each OS thread gets its own VM and GC heap -- values are deep-copied when crossing thread boundaries. See Advanced Features for details.


Thread Operations

current-thread

Syntax: (current-thread)

Returns the thread object representing the currently executing thread.

kaappi> (current-thread)
;=> #<thread main>
kaappi> (thread-name (current-thread))
;=> main

See also: thread?, make-thread


thread?

Syntax: (thread? obj)

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

kaappi> (thread? (current-thread))
;=> #t
kaappi> (thread? (make-thread (lambda () 42)))
;=> #t
kaappi> (thread? 'not-a-thread)
;=> #f

See also: current-thread, make-thread


make-thread

Syntax: (make-thread thunk) | (make-thread thunk name)

Creates a new thread that will execute thunk (a procedure of zero arguments) when started. The optional name is a string used for identification and debugging. The thread is created in a non-started state; call thread-start! to begin execution.

kaappi> (define t (make-thread (lambda () (+ 1 2))))
kaappi> t
;=> #<thread>
kaappi> (define t2 (make-thread (lambda () 'done) "worker"))
kaappi> (thread-name t2)
;=> "worker"

See also: thread-start!, thread-join!


thread-name

Syntax: (thread-name thread)

Returns the name associated with thread, or #f if no name was given at creation time.

kaappi> (thread-name (make-thread (lambda () #f) "my-thread"))
;=> "my-thread"
kaappi> (thread-name (make-thread (lambda () #f)))
;=> #f

See also: make-thread


thread-specific

Syntax: (thread-specific thread)

Returns the thread-specific value associated with thread. Each thread has a single slot for storing an arbitrary value, initially #f.

kaappi> (thread-specific (current-thread))
;=> #f
kaappi> (thread-specific-set! (current-thread) '(my data))
kaappi> (thread-specific (current-thread))
;=> (my data)

See also: thread-specific-set!


thread-specific-set!

Syntax: (thread-specific-set! thread obj)

Sets the thread-specific value of thread to obj. This can be used to associate per-thread data without using global mutable state.

kaappi> (thread-specific-set! (current-thread) 42)
kaappi> (thread-specific (current-thread))
;=> 42

See also: thread-specific


thread-start!

Syntax: (thread-start! thread)

Starts thread and returns the thread object. The thread begins executing the thunk that was passed to make-thread. A thread can only be started once; starting an already-started thread is an error.

Requires --experimental-threads. Without the flag, thread-start! raises an error. The child thread receives a deep copy of the thunk's closure -- mutations in the child are not visible to the parent.

kaappi> (define t (make-thread (lambda () (display "hello\n"))))
kaappi> (thread-start! t)
;=> #<thread>
hello
kaappi> (thread-join! t)
# Run with OS threads enabled
kaappi --experimental-threads program.scm

See also: make-thread, thread-join!


thread-yield!

Syntax: (thread-yield!)

Causes the current thread to voluntarily give up its time slice, allowing the scheduler to run other ready threads. Returns an unspecified value.

kaappi> (thread-yield!)

See also: thread-sleep!, yield (fiber equivalent)


thread-sleep!

Syntax: (thread-sleep! timeout)

Causes the current thread to sleep until timeout. The timeout can be a time object (from seconds->time) or a number of seconds as an inexact real. Other threads continue to run during the sleep.

kaappi> (thread-sleep! 0.5)  ; sleep 500ms
kaappi> (thread-sleep! (seconds->time (+ (time->seconds (current-time)) 1)))

See also: thread-yield!, current-time, seconds->time


thread-terminate!

Syntax: (thread-terminate! thread)

Forcefully terminates thread. Any thread that subsequently calls thread-join! on the terminated thread will receive a terminated-thread-exception. Use with caution: the terminated thread does not get a chance to release resources or unlock mutexes.

kaappi> (define t (make-thread (lambda () (thread-sleep! 100))))
kaappi> (thread-start! t)
;=> #<thread>
kaappi> (thread-terminate! t)

Note

Terminating a thread that holds a mutex leaves the mutex in an abandoned state. Subsequent attempts to lock it will raise an abandoned-mutex-exception.

See also: thread-join!, terminated-thread-exception?


thread-join!

Syntax: (thread-join! thread) | (thread-join! thread timeout) | (thread-join! thread timeout timeout-val)

Blocks the current thread until thread terminates. Returns the result value of the thread's thunk (deep-copied from the child's heap to the caller's heap). If the thread raised an uncaught exception, thread-join! re-raises it wrapped in an uncaught-exception object.

If timeout is given (a time object or number of seconds) and the thread has not terminated by then, a join-timeout-exception is raised -- unless timeout-val is provided, in which case that value is returned instead.

kaappi> (define t (make-thread (lambda () (* 6 7))))
kaappi> (thread-start! t)
;=> #<thread>
kaappi> (thread-join! t)
;=> 42
kaappi> (define t2 (make-thread (lambda () (thread-sleep! 10))))
kaappi> (thread-start! t2)
;=> #<thread>
kaappi> (thread-join! t2 0.1 'timed-out)
;=> timed-out

See also: thread-start!, join-timeout-exception?, uncaught-exception?


Mutexes

mutex?

Syntax: (mutex? obj)

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

kaappi> (mutex? (make-mutex))
;=> #t
kaappi> (mutex? 'not-a-mutex)
;=> #f

See also: make-mutex


make-mutex

Syntax: (make-mutex) | (make-mutex name)

Creates a new mutex in the unlocked state. The optional name is a string used for identification and debugging.

kaappi> (define m (make-mutex "my-lock"))
kaappi> m
;=> #<mutex my-lock>
kaappi> (mutex-state m)
;=> not-abandoned

See also: mutex-lock!, mutex-unlock!


mutex-name

Syntax: (mutex-name mutex)

Returns the name associated with mutex, or #f if no name was given.

kaappi> (mutex-name (make-mutex "lock-1"))
;=> "lock-1"
kaappi> (mutex-name (make-mutex))
;=> #f

See also: make-mutex


mutex-specific

Syntax: (mutex-specific mutex)

Returns the mutex-specific value associated with mutex, initially #f.

kaappi> (define m (make-mutex))
kaappi> (mutex-specific m)
;=> #f
kaappi> (mutex-specific-set! m 'data)
kaappi> (mutex-specific m)
;=> data

See also: mutex-specific-set!


mutex-specific-set!

Syntax: (mutex-specific-set! mutex obj)

Sets the mutex-specific value of mutex to obj.

kaappi> (define m (make-mutex))
kaappi> (mutex-specific-set! m '(resource-info))
kaappi> (mutex-specific m)
;=> (resource-info)

See also: mutex-specific


mutex-state

Syntax: (mutex-state mutex)

Returns the state of mutex. Possible return values:

  • A thread object -- the mutex is locked and owned by that thread
  • The symbol not-owned -- the mutex is locked but not owned
  • The symbol abandoned -- the mutex was abandoned by a terminated thread
  • The symbol not-abandoned -- the mutex is unlocked
kaappi> (define m (make-mutex))
kaappi> (mutex-state m)
;=> not-abandoned
kaappi> (mutex-lock! m)
;=> #t
kaappi> (mutex-state m)
;=> #<thread main>

See also: mutex-lock!, mutex-unlock!


mutex-lock!

Syntax: (mutex-lock! mutex) | (mutex-lock! mutex timeout) | (mutex-lock! mutex timeout thread)

Locks mutex. If the mutex is already locked, the current thread blocks until it becomes available. Returns #t if the lock was acquired.

If timeout is given (a time object or number of seconds) and the mutex cannot be acquired within that time, returns #f. The optional thread argument specifies the new owner of the mutex (defaults to the current thread); passing #f locks the mutex without an owner.

kaappi> (define m (make-mutex))
kaappi> (mutex-lock! m)
;=> #t
kaappi> (mutex-state m)
;=> #<thread main>
kaappi> (mutex-unlock! m)
;=> #t

See also: mutex-unlock!, make-mutex


mutex-unlock!

Syntax: (mutex-unlock! mutex) | (mutex-unlock! mutex condition-variable) | (mutex-unlock! mutex condition-variable timeout)

Unlocks mutex and returns #t. If a condition-variable is given, the current thread is blocked on that condition variable and the mutex is unlocked atomically. If timeout is also given, the thread unblocks after the timeout even if the condition variable was not signaled, and returns #f.

kaappi> (define m (make-mutex))
kaappi> (mutex-lock! m)
;=> #t
kaappi> (mutex-unlock! m)
;=> #t
kaappi> (mutex-state m)
;=> not-abandoned

See also: mutex-lock!, condition-variable-signal!, condition-variable-broadcast!


Condition Variables

condition-variable?

Syntax: (condition-variable? obj)

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

kaappi> (condition-variable? (make-condition-variable))
;=> #t
kaappi> (condition-variable? (make-mutex))
;=> #f

See also: make-condition-variable


make-condition-variable

Syntax: (make-condition-variable) | (make-condition-variable name)

Creates a new condition variable. The optional name is a string used for identification and debugging.

kaappi> (define cv (make-condition-variable "data-ready"))
kaappi> cv
;=> #<condition-variable data-ready>

See also: condition-variable-signal!, condition-variable-broadcast!


condition-variable-name

Syntax: (condition-variable-name condition-variable)

Returns the name associated with condition-variable, or #f if no name was given.

kaappi> (condition-variable-name (make-condition-variable "cv-1"))
;=> "cv-1"
kaappi> (condition-variable-name (make-condition-variable))
;=> #f

See also: make-condition-variable


condition-variable-specific

Syntax: (condition-variable-specific condition-variable)

Returns the condition-variable-specific value, initially #f.

kaappi> (define cv (make-condition-variable))
kaappi> (condition-variable-specific cv)
;=> #f

See also: condition-variable-specific-set!


condition-variable-specific-set!

Syntax: (condition-variable-specific-set! condition-variable obj)

Sets the condition-variable-specific value to obj.

kaappi> (define cv (make-condition-variable))
kaappi> (condition-variable-specific-set! cv 'waiting)
kaappi> (condition-variable-specific cv)
;=> waiting

See also: condition-variable-specific


condition-variable-signal!

Syntax: (condition-variable-signal! condition-variable)

Wakes one thread blocked on condition-variable (if any). If multiple threads are waiting, exactly one is selected to be woken. Which thread is chosen is implementation-dependent. If no threads are waiting, the signal has no effect.

kaappi> (define cv (make-condition-variable))
kaappi> (define m (make-mutex))
kaappi> ;; In a producer/consumer pattern:
kaappi> (condition-variable-signal! cv)

See also: condition-variable-broadcast!, mutex-unlock!


condition-variable-broadcast!

Syntax: (condition-variable-broadcast! condition-variable)

Wakes all threads blocked on condition-variable. If no threads are waiting, the broadcast has no effect.

kaappi> (define cv (make-condition-variable))
kaappi> (condition-variable-broadcast! cv)

See also: condition-variable-signal!, mutex-unlock!


Time

current-time

Syntax: (current-time)

Returns the current time as a time object. This is used with time->seconds and seconds->time for computing timeouts in threading operations.

kaappi> (current-time)
;=> #<time>
kaappi> (time->seconds (current-time))
;=> 1719000000.123

See also: time?, time->seconds, seconds->time


time?

Syntax: (time? obj)

Returns #t if obj is a time object, #f otherwise.

kaappi> (time? (current-time))
;=> #t
kaappi> (time? 42)
;=> #f

See also: current-time


time->seconds

Syntax: (time->seconds time)

Converts a time object to an inexact real number representing seconds since the epoch.

kaappi> (time->seconds (current-time))
;=> 1719000000.123

See also: seconds->time, current-time


seconds->time

Syntax: (seconds->time seconds)

Converts an inexact real number representing seconds since the epoch to a time object. Useful for constructing absolute timeouts for thread-sleep!, mutex-lock!, and thread-join!.

kaappi> (define later (seconds->time (+ (time->seconds (current-time)) 5)))
kaappi> (time? later)
;=> #t

See also: time->seconds, thread-sleep!


Exception Predicates

join-timeout-exception?

Syntax: (join-timeout-exception? obj)

Returns #t if obj is a join-timeout exception, which is raised by thread-join! when the timeout expires before the thread terminates.

kaappi> (define t (make-thread (lambda () (thread-sleep! 100))))
kaappi> (thread-start! t)
;=> #<thread>
kaappi> (guard (e ((join-timeout-exception? e) 'timed-out))
         (thread-join! t (seconds->time (+ (time->seconds (current-time)) 0.1))))
;=> timed-out

See also: thread-join!


abandoned-mutex-exception?

Syntax: (abandoned-mutex-exception? obj)

Returns #t if obj is an abandoned-mutex exception, which is raised when attempting to lock a mutex that was left locked by a thread that has since been terminated.

kaappi> (abandoned-mutex-exception? 'foo)
;=> #f

See also: mutex-lock!, thread-terminate!


terminated-thread-exception?

Syntax: (terminated-thread-exception? obj)

Returns #t if obj is a terminated-thread exception, which is raised by thread-join! when the joined thread was terminated with thread-terminate!.

kaappi> (terminated-thread-exception? 'foo)
;=> #f

See also: thread-join!, thread-terminate!


uncaught-exception?

Syntax: (uncaught-exception? obj)

Returns #t if obj is an uncaught-exception object, which wraps the original exception raised by a thread that terminated abnormally. Use uncaught-exception-reason to extract the original exception.

kaappi> (define t (make-thread (lambda () (error "oops"))))
kaappi> (thread-start! t)
;=> #<thread>
kaappi> (guard (e ((uncaught-exception? e)
                   (error-object-message (uncaught-exception-reason e))))
         (thread-join! t))
;=> "oops"

See also: uncaught-exception-reason, thread-join!


uncaught-exception-reason

Syntax: (uncaught-exception-reason exception)

Returns the original exception object that caused the thread to terminate abnormally. The exception must be an uncaught-exception object (as tested by uncaught-exception?).

kaappi> (define t (make-thread (lambda () (error "something broke" 42))))
kaappi> (thread-start! t)
;=> #<thread>
kaappi> (guard (e ((uncaught-exception? e)
                   (let ((reason (uncaught-exception-reason e)))
                     (list (error-object-message reason)
                           (error-object-irritants reason)))))
         (thread-join! t))
;=> ("something broke" (42))

See also: uncaught-exception?, error-object-message