Threading
*********

SBCL supports a fairly low-level threading interface that maps onto the host operating system's concept of threads or lightweight processes.  This means that threads may take advantage of hardware multiprocessing on machines that have more than one CPU, but it does not allow Lisp control of the scheduler.  This is found in the SB-THREAD package.

This requires Linux (2.6+ or systems with NPTL backports) running on the x86 or x86-64 architecture, or SunOS (Solaris) on the x86.  Support for threading on Darwin (Mac OS X) and FreeBSD on the x86 is experimental.

Threading basics
================

     (make-thread (lambda () (write-line "Hello, world")))

     [file struct-sb-thread-thread.texinfo not included]
     [file var-sb-thread-star-current-thread-star.texinfo not included]
     [file fun-sb-thread-make-thread.texinfo not included]
     [file fun-sb-thread-join-thread.texinfo not included]
     [file condition-sb-thread-join-thread-error.texinfo not included]
     [file fun-sb-thread-join-thread-error-thread.texinfo not included]
     [file fun-sb-thread-thread-alive-p.texinfo not included]
     [file fun-sb-thread-list-all-threads.texinfo not included]
     [file condition-sb-thread-interrupt-thread-error.texinfo not included]
     [file fun-sb-thread-interrupt-thread-error-thread.texinfo not included]
     [file fun-sb-thread-interrupt-thread.texinfo not included]
     [file fun-sb-thread-terminate-thread.texinfo not included]
     [file fun-sb-thread-thread-yield.texinfo not included]

Special Variables
=================

The interaction of special variables with multiple threads is mostly as one would expect, with behaviour very similar to other implementations.

  * global special values are visible across all threads;

  * bindings (e.g. using LET) are local to the thread;

  * threads do not inherit dynamic bindings from the parent thread

The last point means that

     (defparameter *x* 0)
     (let ((*x* 1))
       (sb-thread:make-thread (lambda () (print *x*))))

prints 0 and not 1 as of 0.9.6.

Mutex Support
=============

Mutexes are used for controlling access to a shared resource. One thread is allowed to hold the mutex, others which attempt to take it will be made to wait until it's free. Threads are woken in the order that they go to sleep.

There isn't a timeout on mutex acquisition, but the usual WITH-TIMEOUT macro (which throws a TIMEOUT condition after n seconds) can be used if you want a bounded wait.

     (defpackage :demo (:use "CL" "SB-THREAD" "SB-EXT"))

     (in-package :demo)

     (defvar *a-mutex* (make-mutex :name "my lock"))

     (defun thread-fn ()
       (format t "Thread ~A running ~%" *current-thread*)
       (with-mutex (*a-mutex*)
         (format t "Thread ~A got the lock~%" *current-thread*)
         (sleep (random 5)))
       (format t "Thread ~A dropped lock, dying now~%" *current-thread*))

     (make-thread #'thread-fn)
     (make-thread #'thread-fn)

     [file struct-sb-thread-mutex.texinfo not included]
     [file fun-sb-thread-make-mutex.texinfo not included]
     [file fun-sb-thread-mutex-name.texinfo not included]
     [file fun-sb-thread-mutex-value.texinfo not included]
     [file fun-sb-thread-get-mutex.texinfo not included]
     [file fun-sb-thread-release-mutex.texinfo not included]
     [file macro-sb-thread-with-mutex.texinfo not included]
     [file macro-sb-thread-with-recursive-lock.texinfo not included]

Semaphores
==========

described here should be considered experimental, subject to API changes without notice.

     [file struct-sb-thread-semaphore.texinfo not included]
     [file fun-sb-thread-make-semaphore.texinfo not included]
     [file fun-sb-thread-semaphore-count.texinfo not included]
     [file fun-sb-thread-semaphore-name.texinfo not included]
     [file fun-sb-thread-signal-semaphore.texinfo not included]
     [file fun-sb-thread-wait-on-semaphore.texinfo not included]

Waitqueue/condition variables
=============================

These are based on the POSIX condition variable design, hence the annoyingly CL-conflicting name. For use when you want to check a condition and sleep until it's true. For example: you have a shared queue, a writer process checking ``queue is empty'' and one or more readers that need to know when ``queue is not empty''. It sounds simple, but is astonishingly easy to deadlock if another process runs when you weren't expecting it to.

There are three components:

  * the condition itself (not represented in code)

  * the condition variable (a.k.a waitqueue) which proxies for it

  * a lock to hold while testing the condition

Important stuff to be aware of:

  * when calling condition-wait, you must hold the mutex. condition-wait will drop the mutex while it waits, and obtain it again before returning for whatever reason;

  * likewise, you must be holding the mutex around calls to condition-notify;

  * a process may return from condition-wait in several circumstances: it is not guaranteed that the underlying condition has become true. You must check that the resource is ready for whatever you want to do to it.

     (defvar *buffer-queue* (make-waitqueue))
     (defvar *buffer-lock* (make-mutex :name "buffer lock"))

     (defvar *buffer* (list nil))

     (defun reader ()
       (with-mutex (*buffer-lock*)
         (loop
          (condition-wait *buffer-queue* *buffer-lock*)
          (loop
           (unless *buffer* (return))
           (let ((head (car *buffer*)))
             (setf *buffer* (cdr *buffer*))
             (format t "reader ~A woke, read ~A~%"
                     *current-thread* head))))))

     (defun writer ()
       (loop
        (sleep (random 5))
        (with-mutex (*buffer-lock*)
          (let ((el (intern
                     (string (code-char
                              (+ (char-code #\A) (random 26)))))))
            (setf *buffer* (cons el *buffer*)))
          (condition-notify *buffer-queue*))))

     (make-thread #'writer)
     (make-thread #'reader)
     (make-thread #'reader)

     [file struct-sb-thread-waitqueue.texinfo not included]
     [file fun-sb-thread-make-waitqueue.texinfo not included]
     [file fun-sb-thread-waitqueue-name.texinfo not included]
     [file fun-sb-thread-condition-wait.texinfo not included]
     [file fun-sb-thread-condition-notify.texinfo not included]
     [file fun-sb-thread-condition-broadcast.texinfo not included]

Sessions/Debugging
==================

If the user has multiple views onto the same Lisp image (for example, using multiple terminals, or a windowing system, or network access) they are typically set up as multiple sessions such that each view has its own collection of foreground/background/stopped threads. A thread which wishes to create a new session can use sb-thread:with-new-session to remove itself from the current session (which it shares with its parent and siblings) and create a fresh one. # See also sb-thread:make-listener-thread.

Within a single session, threads arbitrate between themselves for the user's attention.  A thread may be in one of three notional states: foreground, background, or stopped.  When a background process attempts to print a repl prompt or to enter the debugger, it will stop and print a message saying that it has stopped.  The user at his leisure may switch to that thread to find out what it needs.  If a background thread enters the debugger, selecting any restart will put it back into the background before it resumes.  Arbitration for the input stream is managed by calls to sb-thread:get-foreground (which may block) and sb-thread:release-foreground.

sb-ext:quit terminates all threads in the current session, but leaves other sessions running.

Foreign threads
===============

Direct calls to pthread_create (instead of MAKE-THREAD) create threads that SBCL is not aware of, these are called foreign threads. Currently, it is not possible to run Lisp code in such threads. This means that the Lisp side signal handlers cannot work. The best solution is to start foreign threads with signals blocked, but since third party libraries may create threads, it is not always feasible to do so. As a workaround, upon receiving a signal in a foreign thread, SBCL changes the thread's sigmask to block all signals that it wants to handle and resends the signal to the current process which should land in a thread that does not block it, that is, a Lisp thread.

The resignalling trick cannot work for synchronously triggered signals (SIGSEGV and co), take care not to trigger any. Resignalling for synchronously triggered signals in foreign threads is subject to --lose-on-corruption, see Runtime Options.

Implementation (Linux x86/x86-64)
=================================

Threading is implemented using pthreads and some Linux specific bits like futexes.

On x86 the per-thread local bindings for special variables is achieved using the %fs segment register to point to a per-thread storage area. This may cause interesting results if you link to foreign code that expects threading or creates new threads, and the thread library in question uses %fs in an incompatible way. On x86-64 the r12 register has a similar role.

Queues require the sys_futex() system call to be available: this is the reason for the NPTL requirement.  We test at runtime that this system call exists.

Garbage collection is done with the existing Conservative Generational GC.  Allocation is done in small (typically 8k) regions: each thread has its own region so this involves no stopping. However, when a region fills, a lock must be obtained while another is allocated, and when a collection is required, all processes are stopped.  This is achieved by sending them signals, which may make for interesting behaviour if they are interrupted in system calls.  The streams interface is believed to handle the required system call restarting correctly, but this may be a consideration when making other blocking calls e.g. from foreign library code.

Large amounts of the SBCL library have not been inspected for thread-safety.  Some of the obviously unsafe areas have large locks around them, so compilation and fasl loading, for example, cannot be parallelized.  Work is ongoing in this area.

A new thread by default is created in the same POSIX process group and session as the thread it was created by.  This has an impact on keyboard interrupt handling: pressing your terminal's intr key (typically Control-C) will interrupt all processes in the foreground process group, including Lisp threads that SBCL considers to be notionally `background'.  This is undesirable, so background threads are set to ignore the SIGINT signal.

sb-thread:make-listener-thread in addition to creating a new Lisp session makes a new POSIX session, so that pressing Control-C in one window will not interrupt another listener - this has been found to be embarrassing.