No results
8
Thread Rules
dxtr edited this page 2026-10-17 10:29:51 +02:00
Table of Contents
Thread Rules
What may be shared
Every object of cl-threads may be shared between threads, because that is what they are for, with two exceptions.
| Object | Rules |
|---|---|
job-pool |
Any thread may give it work, wait for work, and ask about it. Destroy it from a thread that is not one of its workers. |
job-group |
Any thread may submit jobs to it and ask jobs-done-p. Only one thread at a time may wait for it with wait-for-jobs. |
worker-local |
The object may be shared. Each value belongs to its worker while jobs run; read the values elsewhere only when no job that uses them is running. |
thread |
Any thread may join one made by make-thread, any number of times, ask any thread its name or whether it has finished, and interrupt any thread. |
lock |
Any thread may take it. Not recursive. |
semaphore |
Any thread may signal it or wait on it. |
What a job may do
A job runs on a worker thread. It may:
- call
parallel-foron its own pool, and submit to and wait for groups of its own pool. The worker runs the awaited jobs itself while it waits, so this never deadlocks unless the waits form a cycle; - use locks and semaphores, and make threads;
- call
worker-local-valuefor worker-locals of its pool.
It must not:
- destroy its own pool, which signals
threads-error; - wait on a semaphore for a signal that only another job of the same pool will give. A worker blocked on a semaphore runs nothing, so if every worker waits like this, the pool stops. Wait for the other job with its group instead, whose waiting runs it. Without fibers, which SBCL lacks, a scheduler cannot fix this in general;
- rely on dynamic bindings of the thread that submitted it. Workers see global values, plus the pool's
:bindings. See Job Pools.
Visibility
When a thread learns that a job has finished, it sees what the job wrote:
- after
parallel-fororwait-for-jobsreturns; - after
jobs-done-preturns true; - after
join-threadreturns, for a thread's writes; - after
wait-on-semaphorereturns true, for writes made before the matchingsignal-semaphore; - inside
with-lock, for writes made inside earlierwith-locks of the same lock.
Allocation
| Operation | Allocates |
|---|---|
parallel-for |
Nothing, once the pool has warmed up. |
submit-parallel-for on a reused group, then wait-for-jobs |
Nothing, once warmed up. |
jobs-done-p, worker-index, worker-local-value, with-lock |
Nothing. |
make-job-group, make-worker-local, make-thread, make-lock, make-semaphore |
The new object. Make these once, not per frame. |
worker-local-values |
A fresh list. |
The function you pass counts separately: a closure made each time allocates, and one made once does not. See Parallel For.
Queued jobs are kept in task records that the pool recycles. The pool makes a new one only when more jobs are queued at once than ever before, and keeps it, so after the first large burst queueing no longer allocates.
Underneath
cl-threads is built on SBCL's own threads. What it takes from SBCL is confined to one internal package, cl-threads/sys:
| What | How |
|---|---|
| Threads | sb-thread:make-thread |
| Joining a thread | sb-thread:join-thread, which wakes every thread that waits for the same thread |
| Whether a thread has ended | sb-thread:thread-alive-p |
| Locks | sb-thread:mutex |
| Semaphores | sb-thread:semaphore |
| Waiting with a zero timeout | sb-thread:try-semaphore, since SBCL's wait takes only a positive timeout |
| Signalling a count of 0 | nothing, since SBCL's own signal takes a count of 1 or more |
| Interrupting a thread | sb-thread:interrupt-thread, with interrupts enabled around the function |
| Clearing the stack a worker's job used | sb-sys:scrub-control-stack, since SBCL's collector scans stacks conservatively |
| Processor count | sysconf on Linux, GetActiveProcessorCount on Windows; unknown elsewhere |