Open-source project
cameron314/readerwriterqueue avatar
cameron314/readerwriterqueue

readerwriterqueue: a single-producer, single-consumer lock-free queue for C++

A fast single-producer, single-consumer lock-free queue for C++

4,626 stars738 forksC++NOASSERTION

At a glance

What is it?
moodycamel's ReaderWriterQueue is a header-only C++11 queue that avoids locks and compare-and-swap loops by assuming exactly one producer thread and one consumer thread. Here is how it is structured, how to install it, and where that assumption breaks.
Who is it for?
Adopt readerwriterqueue when your topology is genuinely one producer thread and one consumer thread and you want a queue that is wait-free on x86 and allocates up front. Do not adopt it if producers or consumers can multiply, if you need to change thread roles at runtime, or if you target a platform with weak memory ordering such as DEC Alpha, which the README explicitly rules out.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 23 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The two-thread constraint that defines readerwriterqueue

Most lock-free queues pay for generality. They let any thread enqueue and any thread dequeue, and the price is a compare-and-swap loop, retries under contention, and a memory reclamation scheme that has to handle the case where a producer is mid-write while a consumer frees the node. readerwriterqueue refuses that trade. The README states it "only supports a two-thread use case (one consuming, and one producing)" and that the threads cannot switch roles.

That single restriction is what buys the rest. There is no CAS loop anywhere in the enqueue or dequeue path, so both operations are wait-free and O(1), not counting memory allocation. On x86 the memory barriers compile down to no-ops, which means enqueue and dequeue reduce to a series of loads, stores and branches. The intended reader is an engineer who controls both ends of a pipeline: an audio thread feeding a UI thread, a network receive thread handing packets to a worker, a logging thread draining a buffer written by the main loop. If you cannot promise one writer and one reader, this is the wrong library and the README points you at the author's concurrentqueue instead.

How the queue stores elements without a lock-free memory manager

The design decision that shapes everything else is that the queue owns its slots rather than owning pointers to separately allocated nodes. The README describes it as "fully generic (templated container of any type) -- just like std::queue, you never need to allocate memory for elements yourself (which saves you the hassle of writing a lock-free memory manager to hold the elements you're queueing)". Memory is allocated up front in contiguous blocks, and the constructor argument sets that initial capacity.

Two methods sit on top of that storage with different guarantees. try_enqueue never allocates: it succeeds only if the queue already has an empty slot, so it is the method you call from a real-time thread where a malloc would be unacceptable. enqueue will allocate when the queue is full and grow it. try_emplace and emplace are convenience wrappers that construct the element in place. The blocking variant, BlockingReaderWriterQueue, adds wait_dequeue and wait_dequeue_timed on the exact same API, and the repository also ships readerwritercircularbuffer.h with a fixed-slot BlockingReaderWriterCircularBuffer that supports blocking on enqueue as well as dequeue.

The failure mode hiding in the blocking API is worth stating plainly. The README warns that wait_dequeue blocks indefinitely while the queue is empty, so you should only call it if another element is certain to arrive eventually or the queue has static lifetime, because destroying the queue while a thread waits on it is undefined behaviour.

Installing readerwriterqueue and dequeuing your first item

The library is header-only. The README says to "simply drop the readerwriterqueue.h (or readerwritercircularbuffer.h) and atomicops.h files into your source code and include them". A modern compiler is required, listed as MSVC2010+, GCC 4.7+, ICC 13+, or any C++11 compliant compiler. GCC 4.6 is called out specifically: it has a bug that prevents the atomic fence primitives from working correctly.

If you prefer not to vendor the headers, the README gives a CMake path using FetchContent. Declare the dependency, make it available, and link the target:

cmake
include(FetchContent)

FetchContent_Declare(
  readerwriterqueue
  GIT_REPOSITORY    https://github.com/cameron314/readerwriterqueue
  GIT_TAG           master
)

FetchContent_MakeAvailable(readerwriterqueue)

add_library(my_target main.cpp)
target_link_libraries(my_target PUBLIC readerwriterqueue)

With the target linked, include the header by name and construct a queue with an initial capacity. The README's example reserves space for at least 100 elements up front, then enqueues and dequeues:

cpp
#include <readerwriterqueue.h>

int main()
{
    moodycamel::ReaderWriterQueue<int> q(100);
    q.enqueue(17);
    bool succeeded = q.try_enqueue(18);
    int number;
    succeeded = q.try_dequeue(number);
}

After the first try_dequeue, number holds 17. Note that try_enqueue only succeeds when a slot is free, while enqueue will allocate if the queue is full. There is also a peek method for the consumer side that returns a pointer to the front item, or nullptr when the queue is empty.

The alternative install route is the system include directory. From the build folder the README runs cmake and make install, after which the header is included as readerwriterqueue/readerwriterqueue.h instead of readerwriterqueue.h. That path difference is a common source of a first-build failure: the include line changes depending on which install method you chose.

Where readerwriterqueue is the wrong tool

The role restriction is absolute. If two threads can both produce at different times, or a thread that produced earlier later needs to consume, the queue is not safe to use. There is no runtime check for this; the library has no way to detect that you violated the contract. A design that starts with one producer and one consumer often grows a second producer later, and that change is silent in review because the API looks identical to std::queue.

The platform caveat is equally concrete. The README says the queue should only be used where aligned integer and pointer access is atomic, which it notes covers x86/x86-64, ARM and PowerPC, and it explicitly excludes DEC Alpha because of its weak memory ordering. It also states the code has only been tested on x86(-64). If you are building for a less common architecture, you are relying on the memory model reasoning rather than on a test run.

The blocking queue adds a lifetime hazard. Because wait_dequeue blocks indefinitely on an empty queue, a shutdown path that destroys the queue while a consumer is parked inside wait_dequeue is undefined behaviour. The timed variant, wait_dequeue_timed, is the escape hatch: it returns after a timeout and lets the consumer check a shutdown flag. A team that adopts BlockingReaderWriterQueue without designing that exit path has adopted a shutdown bug.

readerwriterqueue against Boost.Lockfree and the author's own concurrentqueue

The closest general alternative is Boost.Lockfree, which offers both spsc_queue and a multi-producer, multi-consumer queue. The difference is in what each one assumes and what it costs. Boost's spsc_queue, like readerwriterqueue, is built for one producer and one consumer, but it is a fixed-capacity ring buffer with no growth path, while readerwriterqueue's enqueue will allocate and grow the queue when it is full. readerwriterqueue also offers try_enqueue as a guarantee that no allocation happens, which matters when the producing thread cannot tolerate a malloc. Boost's multi-producer queue, by contrast, uses compare-and-swap loops and returns false when a push fails, pushing retry logic into your code; readerwriterqueue's enqueue and dequeue are wait-free precisely because it does not support that case.

The second alternative is the same author's concurrentqueue, which the README links as the general-purpose multi-producer, multi-consumer option. Choosing between them is not a question of speed but of topology: if you need more than one producer or more than one consumer, concurrentqueue is the one the README directs you to. readerwriterqueue is the narrower tool that gets its properties from refusing that generality.

Licence and the cost of upgrading

The README says to see LICENSE.md for the licence and describes it as simplified BSD. The repository metadata reports the licence as NOASSERTION, which means GitHub's classifier did not match the file to a known SPDX identifier, so the text of LICENSE.md is the thing to read rather than the metadata badge. Simplified BSD terms are generally permissive, but whether your organisation accepts them, and what attribution your distribution requires, is a question for your own review process rather than something this article can settle.

The upgrade cost is low in the ordinary case, because the library is two headers you can copy. There is no compiled artifact to rebuild and no ABI to match. The releases tell a different story about cadence: v1.0.7 landed on 2025-04-28, v1.0.6 on 2021-11-28, and v1.0.5 on 2021-05-01. That is a gap of more than three years between v1.0.6 and v1.0.7, so a team pinning to a release tag should expect long stretches with no new tag, and should read the commits rather than wait for a release. The last push to the repository was on 2026-09-07. If you use FetchContent with GIT_TAG master as the README shows, you are tracking that branch and will pick up changes without a release in between; pinning to a tag trades that freshness for reproducibility.

Editorial conclusion

Adopt readerwriterqueue when your topology is genuinely one producer thread and one consumer thread and you want a queue that is wait-free on x86 and allocates up front. Do not adopt it if producers or consumers can multiply, if you need to change thread roles at runtime, or if you target a platform with weak memory ordering such as DEC Alpha, which the README explicitly rules out. Before wiring it in, verify three things in your own code: that the queue outlives every thread that calls wait_dequeue, that your compiler is GCC 4.7 or newer, and that your CMake target links readerwriterqueue rather than copying headers into a directory the include path does not cover.

Frequently asked questions

Is std::queue thread-safe?

No. readerwriterqueue exists precisely because std::queue has no synchronisation, and the README positions its own queue as a templated container you use like std::queue but across one producer thread and one consumer thread. Even then, the guarantee is limited to that two-thread case.

How can I implement lock-free queues in C++?

One approach is to restrict the problem to a single producer and a single consumer, which is what readerwriterqueue does. The README states that enqueue and dequeue are wait-free with no compare-and-swap loop, and that on x86 the memory barriers compile down to no-ops. The repository ships readerwriterqueue.h, readerwritercircularbuffer.h and atomicops.h.

How can I create a thread-safe queue in C++ with readerwriterqueue?

Include readerwriterqueue.h, construct a moodycamel::ReaderWriterQueue with an initial capacity, and call enqueue from the producer thread and try_dequeue from the consumer thread. If you need blocking, use BlockingReaderWriterQueue and its wait_dequeue or wait_dequeue_timed methods.

Official sources

  1. cameron314/readerwriterqueue on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cameron314-readerwriterqueue.svg)](https://hysenlabs.com/projects/cameron314-readerwriterqueue)