https://github.com/elijahr/lockfreequeues
Lock-free queue implementations for Nim.
https://github.com/elijahr/lockfreequeues
circular-buffer lock-free mpmc mpsc nim queue ring-buffer spsc
Last synced: 23 days ago
JSON representation
Lock-free queue implementations for Nim.
- Host: GitHub
- URL: https://github.com/elijahr/lockfreequeues
- Owner: elijahr
- License: mit
- Created: 2020-07-03T00:20:00.000Z (almost 6 years ago)
- Default Branch: devel
- Last Pushed: 2026-04-26T09:35:01.000Z (28 days ago)
- Last Synced: 2026-04-26T10:16:12.606Z (28 days ago)
- Topics: circular-buffer, lock-free, mpmc, mpsc, nim, queue, ring-buffer, spsc
- Language: Nim
- Homepage:
- Size: 1.24 MB
- Stars: 47
- Watchers: 3
- Forks: 5
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Authors: AUTHORS
Awesome Lists containing this project
- awesome-nim - lockfreequeues - Lock-free queue implementations for Nim. (Operating System / IO)
README
[](https://github.com/elijahr/lockfreequeues/actions/workflows/build.yml)
# lockfreequeues
Lock-free queues for Nim. Bounded queues are ring buffers; unbounded queues are
linked segments reclaimed via [DEBRA](https://github.com/elijahr/nim-debra).
All variants cover SPSC, SPMC, MPSC, and MPMC.
API documentation:
## Why this library
If two threads need to hand items to each other and you cannot afford a mutex,
the answer is a lock-free queue. Picking the right one is the hard part: do you
have one producer or many, one consumer or many, a fixed capacity or not? Each
choice changes the algorithm and the cost. `lockfreequeues` ships eight queues
covering every cell of that grid, with a uniform API and verified ordering
guarantees.
A short vocabulary first.
- **Wait-free**: every thread completes its operation in a bounded number of
steps, regardless of what other threads do. The strongest progress guarantee.
- **Lock-free**: at least one thread makes progress on every step. Individual
threads may retry, but the system never stalls.
Wait-free is preferable when you can get it; lock-free is what you get with
contended CAS loops. Both are stronger than mutex-based code, which can stall
the whole system if a holder is preempted.
## Installation
```sh
nimble install lockfreequeues
```
## Quick Start
### Bounded SPSC
```nim
import options
import lockfreequeues
# Bounded single-producer, single-consumer queue, capacity 16
var queue = initSipsic[16, int]()
discard queue.push(42)
discard queue.push(123)
let item = queue.pop() # some(42)
assert item == some(42)
```
### Unbounded MPMC
The MP/MC unbounded variants need a `DebraManager` for safe segment
reclamation and a per-thread handle for every producer and consumer.
```nim
import options
import debra
import lockfreequeues
var manager = initDebraManager[4]()
var queue = newUnboundedMupmuc[64, int, 4](addr manager)
let producerHandle = registerThread(manager)
let consumerHandle = registerThread(manager)
var producer = queue.getProducer(producerHandle)
var consumer = queue.getConsumer(consumerHandle)
producer.push(42)
let item = consumer.pop() # some(42)
assert item == some(42)
```
See [`examples/`](examples/) for full multi-threaded examples and patterns
(audio buffer, job scheduler, event collector, task fan-out).
## Choosing a queue
| Queue | P | C | Push | Pop | Bounded? | Needs `DebraManager`? | Per-thread handle? |
|--------------------|------|------|-----------|-----------|----------|-----------------------|--------------------|
| `Sipsic` | 1 | 1 | wait-free | wait-free | yes | no | no |
| `Sipmuc` | 1 | many | wait-free | lock-free | yes | no | no |
| `Mupsic` | many | 1 | lock-free | wait-free | yes | no | no |
| `Mupmuc` | many | many | lock-free | lock-free | yes | no | no |
| `UnboundedSipsic` | 1 | 1 | wait-free | wait-free | no | no | no |
| `UnboundedSipmuc` | 1 | many | wait-free | lock-free | no | yes | consumer side |
| `UnboundedMupsic` | many | 1 | lock-free | wait-free | no | yes | producer side |
| `UnboundedMupmuc` | many | many | lock-free | lock-free | no | yes | both |
`UnboundedSipsic` is special: with one producer and one consumer the consumer
is the only freer, so it does not need DEBRA. Every other unbounded variant
does, because multiple threads can race to detach a segment.
### Bounded vs unbounded
Bounded queues are ring buffers with compile-time capacity. Use them when:
- memory usage must be predictable;
- you are working in embedded or real-time systems;
- producer and consumer counts are known at compile time.
Unbounded queues are linked segments that grow as needed. Use them when:
- workload is bursty or unpredictable;
- producer or consumer threads are created dynamically;
- some memory growth is acceptable in exchange for never blocking on a full queue.
## Dependencies
- [`debra`](https://github.com/elijahr/nim-debra) `>= 0.3.0` for epoch-based
reclamation in the unbounded multi-thread queues. `nim-debra` is a
general-purpose DEBRA+ implementation; nothing about it is specific to this
library, and it can be reused as the reclamation backend for any lock-free
data structure you build.
- [`typestates`](https://github.com/elijahr/nim-typestates) `>= 0.3.1` for the
slot-ownership state machines that back push and pop.
## Compile-time options
| Flag | Default | Effect |
|--------------------------------------------|---------|-----------------------------------------------------------------------------------------|
| `-d:allowNonLockFreeQueueItems` | off | Disable the arc/orc compile-time check that rejects `ref` item types. |
| `-d:nimEnforceLockFreeAtomics` | off | Nim flag; fail compilation if any atomic operation falls back to spinlocks. |
| `-d:LockFreeQueuesAdvanceEvery=N` | 64 | DEBRA epoch-advance cadence for unbounded queues' Eager reclamation per-pop fast path. |
## Thread safety
The one rule that bites first: on `arc` / `orc`, `ref` item types fail to
compile. Reference counting on those memory managers can fall back to
spinlocks, which would defeat the lock-free guarantee. Use a value type, a
`ptr T`, or compile with `-d:allowNonLockFreeQueueItems` if you accept the
trade-off.
The full safety model — slot-ownership typestates, why the queue itself is
lock-free even when items are not, and the matrix of MM x sanitiser
combinations under CI — lives in
[`docs/safety-model.md`](docs/safety-model.md). The typestate transitions are
documented in
[`docs/slot-ownership-typestates.md`](docs/slot-ownership-typestates.md).
## Benchmarks
Throughput and latency results are checked into
[`benchmarks/results/latest.json`](benchmarks/results/latest.json) and rendered
into the table below. Re-run the suite with `nimble benchmarks`, then update
this section with `nim r benchmarks/render_readme.nim`.
_Platform: macosx arm64, 8 cores, 2025-12-03T22:24:55Z._
| implementation | threads | throughput (ops/ms) | p50 latency (ns) |
|----------------|---------|---------------------|------------------|
| `lockfreequeues/Sipsic` | 1P/1C | 7411.0 | 292 |
| `nim/channels` | 1P/1C | 1199.7 | — |
| `nim/channels` | 2P/2C | 815.8 | — |
| `nim/channels` | 4P/4C | 1779.5 | — |
_Numbers regenerated by `nim r benchmarks/render_readme.nim` from `benchmarks/results/latest.json`._
See [`benchmarks/`](benchmarks/) for the full suite, methodology, and
adapter implementations.
## Examples
Examples are in [`examples/`](examples/) and can be run with:
```sh
nimble examples
```
## Running tests
```sh
nimble test
```
CI (see [`.github/workflows/build.yml`](.github/workflows/build.yml)) runs the
suite on:
- Runners: `ubuntu-24.04` (x86_64), `ubuntu-24.04-arm` (native arm64),
`macos-latest` (arm64).
- Memory managers: `arc`, `orc`, `refc`, `atomicArc`.
- Backends: C and C++.
- Sanitisers: ThreadSanitizer (TSAN) on `atomicArc`, AddressSanitizer (ASAN).
- Lock-free atomic enforcement: `-d:nimEnforceLockFreeAtomics` lane on `arc`
and `orc`.
192 tests across the bounded, unbounded, threaded, and lock-free-check suites.
## Contributing
Pull requests and issues welcome. See
[CONTRIBUTING.md](CONTRIBUTING.md) for the contribution workflow.
## Changelog
See [CHANGELOG.md](CHANGELOG.md). The current release is
[3.2.0](CHANGELOG.md#320---2026-04-27).
## References
- Juho Snellman, ["I've been writing ring buffers wrong all these years"](https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/)
([alt](https://web.archive.org/web/20200530040210/https://www.snellman.net/blog/archive/2016-12-13-ring-buffers/)).
- Mamy Ratsimbazafy, [research on SPSC channels](https://github.com/mratsim/weave/blob/master/weave/cross_thread_com/channels_spsc.md#litterature)
for weave.
- Henrique F. Bucher, ["Yes, You Have Been Writing SPSC Queues Wrong Your Entire Life"](http://www.vitorian.com/x1/archives/370)
([alt](https://web.archive.org/web/20191225164231/http://www.vitorian.com/x1/archives/370)).
- Maged M. Michael and Michael L. Scott, "Simple, Fast, and Practical
Non-Blocking and Blocking Concurrent Queue Algorithms" (PODC 1996).
- Dmitry Vyukov's writings on bounded MPMC ring buffers and CAS-based
coordination patterns.
- Trevor Brown, ["Reclaiming Memory for Lock-Free Data Structures: There has to
be a Better Way"](https://www.cs.utoronto.ca/~tabrown/debra/) (DEBRA, the
reclamation scheme used by the unbounded queues).
Many thanks to Mamy Ratsimbazafy for reviewing the initial release and
offering suggestions.
## License
MIT — see [LICENSE](LICENSE).