Reading — step 1 of 5
Learn
Common Lisp standard doesn't define threads — but every modern implementation has them, and bordeaux-threads is the portable wrapper.
Why CL threads matter
Common Lisp threads are real OS threads — preemptive, parallel on multi-core. Different from Lua coroutines (cooperative) or Python's GIL-bound threads.
Basic thread
(ql:quickload :bordeaux-threads)
(let ((thread (bt:make-thread
(lambda () (format t "hello from thread~%")))))
(bt:join-thread thread))
Mutexes
(defparameter *lock* (bt:make-lock))
(defparameter *count* 0)
(bt:with-lock-held (*lock*)
(incf *count*))
with-lock-held ensures the lock is released even on error.
Condition variables
(defparameter *cv* (bt:make-condition-variable))
(defparameter *queue* '())
(defparameter *queue-lock* (bt:make-lock))
;; Producer:
(bt:with-lock-held (*queue-lock*)
(push item *queue*)
(bt:condition-notify *cv*))
;; Consumer:
(bt:with-lock-held (*queue-lock*)
(loop while (null *queue*)
do (bt:condition-wait *cv* *queue-lock*))
(pop *queue*))
Atomic operations
SBCL has its own atomics module:
(sb-ext:atomic-incf *counter*)
(sb-ext:atomic-update *cell* (lambda (old) (* old 2)))
Channels
For higher-level concurrency, use chanl (CSP-style) or lparallel (parallel map/reduce):
(ql:quickload :lparallel)
(setf lparallel:*kernel* (lparallel:make-kernel 4))
(lparallel:pmapcar (lambda (n) (* n n)) (loop for i from 1 to 1000 collect i))
pmapcar — parallel mapcar across CPUs. Good for embarrassingly parallel work.
Special variables and threads
Each thread has its own dynamic binding stack. So (let ((*log-level* :error)) ...) only affects the thread that ran the let. Useful for thread-local config.
Race conditions in Lisp
Lisp has the same hazards as any language with shared mutable state. Be careful with:
- Hash tables — use a lock or
:synchronized t(SBCL) - Lists —
cons,setfaren't atomic across operations - Special variables in let — fine within one thread; cross-thread, use atomics
Practical advice
- Default to single-threaded; add threads when profiling shows CPU underutilization
- Lparallel for compute-bound parallel work
- Bordeaux for explicit thread management (servers, GUIs)
- Avoid sharing state — pass data through queues/channels
We can't run threads in the Judge0 sandbox (single-threaded execution), but the patterns above are how real CL apps use multi-core.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…