← Projects

Lock-free SPSC Ring Buffer

A lock-free queue for exactly one producer thread and one consumer thread, written in C++ to understand memory ordering in practice.

Design

The buffer is a fixed array with two indices: the producer writes at write_index, and the consumer reads at read_index. Each index is written by only one thread, which is what makes a lock-free design possible without compare-and-swap. put() and get() never block: they return immediately if the queue is full or empty.

The capacity must be a power of two, so indices wrap with a bitmask (index & (size - 1)) instead of a slower modulo. One slot is always left empty, so that “full” and “empty” can be told apart from the two indices alone. The effective capacity is therefore size − 1.

Memory ordering

The correctness argument rests on acquire/release pairs. Each side reads its own index with relaxed ordering, since only it ever writes that index. It reads the other side’s index with acquire, and publishes its own index with release after writing or reading the data. The release by the producer and the acquire by the consumer guarantee that once the consumer sees the new write_index, it also sees the data written into that slot. The same pairing in the other direction guarantees the producer never overwrites a slot the consumer is still reading.

Benchmark (10M operations, Ryzen 5 3550H): the mutex-guarded std::queue took 10.3–11.7 s per run and the SPSC queue took 1.3–1.6 s, roughly a 7–8× speedup.

Bar chart of five benchmark runs: mutex-guarded queue around 10–12 seconds, lock-free ring buffer around 1.3–1.6 seconds

Five runs of 10M operations each, from the repository README.

Limits. SPSC only: with more than one producer or consumer the behaviour is undefined. There is no resizing, and contention is handled by spinning rather than sleeping.