Threads in SBCL

Working with threads in SBCL isn't obvious in some respects. I'd like to use this post to collect some hints for people starting out with multi-threaded programming in SBCL. Prerequisite: SBCL compiled with threads (still not the default). Check with (not (null (member :sb-thread *features*))).

Usage

All thread stuff lies in the SB-THREAD package, so you need to import it when you don't want to do prefixing:
(use-package :sb-thread)

Creating a thread

(make-thread (lambda () ...) :name "optional name")
Spawns a new thread executing the lambda and return it. The thread will silently terminate when the lambda returns. You can wait for its completion using JOIN-THREAD.

Listing threads

(list-all-threads)
'nuff said.

Debugging threads

Use RELEASE-FOREGROUND to switch between multiple threads waiting for input:
CL-USER(15): (make-thread (lambda () (break)))
 
debugger invoked on a SIMPLE-CONDITION in thread #< thread RUNNING {AA9E831}>:
  break
 
#< thread RUNNING {AA9E831}>
CL-USER(16): (release-foreground)
 
Resuming thread #< thread RUNNING {A93CD49}>
 
Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.
 
restarts (invokable by number or by possibly-abbreviated name):
  0: [CONTINUE        ] Return from BREAK.
  1: [TERMINATE-THREAD] Terminate this thread (#< thread RUNNING {A93CD49}>)
 
(BREAK "break")3
0] (release-foreground)
Resuming thread #< thread "initial thread" RUNNING {A6DD551}>
CL-USER(17):

Cleaning up

Perhaps this is obvious to someone familiar with the stack semantics of threading:
CL-USER(25): (make-thread (lambda () (unwind-protect (break) (format t "cleanup~%"))))
 
#<thread RUNNING {AB89DB9}>
CL-USER(26):
debugger invoked on a SIMPLE-CONDITION in thread #< thread RUNNING {AB89DB9}>:
  break
(release-foreground)
Resuming thread #< thread RUNNING {AB89DB9}>
 
Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.
 
restarts (invokable by number or by possibly-abbreviated name):
  0: [CONTINUE        ] Return from BREAK.
  1: [TERMINATE-THREAD] Terminate this thread (#< thread RUNNING {AB89DB9}>)
 
(BREAK "break")
0]
1
Resuming thread #< thread "initial thread" RUNNING {A6DD551}>
1
CL-USER(27): cleanup
</thread>

Miscellaneous notes

Hash tables

Somewhere in time SBCL dropped locking for hash tables to give the user the freedom to implement the level of locking they need. So you need to protect your hash tables for concurrent access or else.

Thread model

There are two fundamental models for a thread framework: rely on the native OS facilities for threading or implement some sort of concurrency scheduling yourself. Allegro uses the latter method while SBCL and ECL use native threading.

Comments

  1. October 4th, 2008 | 8:01 pm

    When speaking about threads, locks and synchronization objects and some other aspects should also be mentioned.

    Sbcl provides mutexes, semaphores and condition variables. In principle, it is enough to implement other synchronization objects. But it lacks message queues, worker thread pool, etc.

    Also, it is worth to mention special variables. In SBCL, all threads share values of special variables, but have independent bindings and do not inherit bindings. So, dynamic variables act as thread-local variables. This has some consequences, for example, with standard IO. For example, Slime rebinds *standard-output* to another value, and newly created threads print to another stream.

Leave a reply