Convert Figma logo to code with AI

cameron314 logoreaderwriterqueue

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

4,194
696
4,194
19

Top Related Projects

A bounded multi-producer multi-consumer concurrent queue written in C++11

29,642

An open-source C++ library developed and used at Facebook.

Concurrent data structures in C++

A bounded single-producer single-consumer wait-free and lock-free queue written in C++11

C++14 lock-free queue.

Quick Overview

The cameron314/readerwriterqueue is a fast single-producer, single-consumer lock-free queue for C++. It's designed to be highly efficient for inter-thread communication, particularly in scenarios where you have one thread producing data and another consuming it.

Pros

  • Lock-free implementation, resulting in high performance
  • Header-only library, easy to integrate into projects
  • Supports both move semantics and copy semantics
  • Well-documented and thoroughly tested

Cons

  • Limited to single-producer, single-consumer scenarios
  • May not be suitable for complex multi-threaded architectures
  • Requires C++11 or later
  • Not designed for distributed systems or inter-process communication

Code Examples

  1. Basic usage:
#include "readerwriterqueue.h"

moodycamel::ReaderWriterQueue<int> q(100);  // Reserve space for at least 100 elements

int item;
q.enqueue(42);
bool succeeded = q.try_dequeue(item);
if (succeeded) {
    assert(item == 42);
}
  1. Using with move semantics:
#include "readerwriterqueue.h"
#include <string>

moodycamel::ReaderWriterQueue<std::string> q;

std::string str = "Hello, World!";
q.enqueue(std::move(str));  // str is now empty

std::string result;
q.try_dequeue(result);  // result now contains "Hello, World!"
  1. Bulk operations:
#include "readerwriterqueue.h"
#include <vector>

moodycamel::ReaderWriterQueue<int> q;

std::vector<int> items = {1, 2, 3, 4, 5};
q.enqueue_bulk(items.begin(), items.size());

std::vector<int> results(5);
size_t count = q.try_dequeue_bulk(results.begin(), 5);
assert(count == 5);

Getting Started

To use the readerwriterqueue in your project:

  1. Download the readerwriterqueue.h file from the GitHub repository.
  2. Include the header in your C++ file:
    #include "readerwriterqueue.h"
    
  3. Create a queue instance:
    moodycamel::ReaderWriterQueue<YourDataType> queue;
    
  4. Use enqueue and try_dequeue methods to add and remove items from the queue.

Make sure you're compiling with C++11 or later. No additional compilation flags or linking is required as it's a header-only library.

Competitor Comparisons

A bounded multi-producer multi-consumer concurrent queue written in C++11

Pros of MPMCQueue

  • Supports multiple producers and multiple consumers (MPMC), offering more flexibility than ReaderWriterQueue's single-producer, single-consumer design
  • Implements a lock-free algorithm, potentially providing better performance in high-contention scenarios
  • Offers a blocking interface with timeouts, allowing for more versatile usage patterns

Cons of MPMCQueue

  • May have higher memory overhead due to its more complex structure and padding for cache line alignment
  • Potentially slower for simple single-producer, single-consumer scenarios compared to ReaderWriterQueue's specialized implementation
  • Requires C++11 or later, while ReaderWriterQueue supports older C++ standards

Code Comparison

MPMCQueue:

MPMCQueue<int> q(100);
q.push(42);
int value;
q.pop(value);

ReaderWriterQueue:

ReaderWriterQueue<int> q;
q.enqueue(42);
int value;
q.try_dequeue(value);

Both libraries provide simple interfaces for queue operations, but MPMCQueue's constructor requires specifying the queue size upfront. ReaderWriterQueue uses different method names (enqueue/try_dequeue) compared to MPMCQueue's more standard push/pop nomenclature. The usage patterns are similar, but MPMCQueue's multi-producer, multi-consumer nature allows for more concurrent access scenarios.

29,642

An open-source C++ library developed and used at Facebook.

Pros of folly

  • Comprehensive C++ library with a wide range of utilities and components
  • Actively maintained by Facebook with frequent updates and improvements
  • Extensive documentation and examples for various use cases

Cons of folly

  • Large codebase with many dependencies, potentially increasing build times
  • Steeper learning curve due to the breadth of features and abstractions
  • May introduce unnecessary complexity for projects only needing specific components

Code Comparison

readerwriterqueue:

moodycamel::ReaderWriterQueue<int> q(100);
q.enqueue(42);
int item;
bool success = q.try_dequeue(item);

folly:

folly::MPMCQueue<int> q(100);
q.write(42);
int item;
bool success = q.read(item);

Summary

While readerwriterqueue focuses specifically on efficient lock-free queue implementations, folly offers a broader set of utilities and data structures. readerwriterqueue may be more suitable for projects requiring a lightweight, single-purpose solution, whereas folly provides a comprehensive toolkit for larger-scale C++ development. The choice between the two depends on the specific needs of the project and the desired balance between simplicity and feature richness.

Concurrent data structures in C++

Pros of Junction

  • Supports multiple producers and consumers
  • Provides a wider range of concurrent data structures (queues, maps, sets)
  • Offers more advanced features like memory reclamation and hazard pointers

Cons of Junction

  • More complex API, potentially steeper learning curve
  • Higher memory overhead due to additional features
  • May have slightly lower performance in simple use cases

Code Comparison

ReaderWriterQueue:

moodycamel::ReaderWriterQueue<int> q;
q.enqueue(42);
int item;
bool success = q.try_dequeue(item);

Junction:

junction::ConcurrentQueue<int> q;
q.push(42);
std::optional<int> item = q.pop();

Summary

Junction offers a more comprehensive set of concurrent data structures and advanced features, making it suitable for complex multi-threaded applications. ReaderWriterQueue, on the other hand, provides a simpler, more focused implementation of a lock-free queue, which may be preferable for straightforward producer-consumer scenarios. The choice between the two depends on the specific requirements of your project, balancing features, performance, and ease of use.

A bounded single-producer single-consumer wait-free and lock-free queue written in C++11

Pros of SPSCQueue

  • Optimized for single-producer, single-consumer scenarios, potentially offering better performance in specific use cases
  • Implements a lock-free algorithm, which can reduce contention and improve throughput
  • Provides a simple API with clear semantics for enqueue and dequeue operations

Cons of SPSCQueue

  • Limited to single-producer, single-consumer scenarios, less flexible than ReaderWriterQueue
  • May have higher memory usage due to its ring buffer implementation
  • Lacks some additional features present in ReaderWriterQueue, such as bulk enqueue/dequeue operations

Code Comparison

SPSCQueue:

SPSCQueue<int> q(100);
q.push(42);
int value;
bool success = q.pop(value);

ReaderWriterQueue:

ReaderWriterQueue<int> q;
q.enqueue(42);
int value;
bool success = q.try_dequeue(value);

Both implementations offer similar basic functionality, but ReaderWriterQueue provides more flexibility with its multi-producer, multi-consumer design. SPSCQueue may have a slight edge in performance for specific single-producer, single-consumer scenarios, while ReaderWriterQueue offers a broader range of features and use cases. The choice between the two depends on the specific requirements of the project and the expected usage patterns.

C++14 lock-free queue.

Pros of atomic_queue

  • Higher performance, especially for multi-producer/multi-consumer scenarios
  • More flexible API with support for bulk operations
  • Better memory efficiency due to its compact design

Cons of atomic_queue

  • Less widely adopted and tested compared to readerwriterqueue
  • May have a steeper learning curve for some developers
  • Limited documentation and examples available

Code Comparison

atomic_queue:

AtomicQueue<int, 1024> q;
q.push(42);
int value;
bool success = q.pop(value);

readerwriterqueue:

ReaderWriterQueue<int> q(1024);
q.enqueue(42);
int value;
bool success = q.try_dequeue(value);

Key Differences

  • atomic_queue uses a template parameter for queue size, while readerwriterqueue uses a constructor parameter
  • atomic_queue's push/pop operations are named differently from readerwriterqueue's enqueue/dequeue
  • atomic_queue provides additional bulk operations not present in readerwriterqueue

Use Cases

  • atomic_queue: High-performance scenarios with multiple producers and consumers
  • readerwriterqueue: General-purpose lock-free queue with simpler API and wider adoption

Community and Support

  • readerwriterqueue has a larger user base and more extensive documentation
  • atomic_queue offers potential performance benefits but may require more expertise to implement effectively

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

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

This mini-repository has my very own implementation of a lock-free queue (that I designed from scratch) for C++.

It only supports a two-thread use case (one consuming, and one producing). The threads can't switch roles, though you could use this queue completely from a single thread if you wish (but that would sort of defeat the purpose!).

Note: If you need a general-purpose multi-producer, multi-consumer lock free queue, I have one of those too.

This repository also includes a circular-buffer SPSC queue which supports blocking on enqueue as well as dequeue.

Features

  • Blazing fast
  • Compatible with C++11 (supports moving objects instead of making copies)
  • 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)
  • Allocates memory up front, in contiguous blocks
  • Provides a try_enqueue method which is guaranteed never to allocate memory (the queue starts with an initial capacity)
  • Also provides an enqueue method which can dynamically grow the size of the queue as needed
  • Also provides try_emplace/emplace convenience methods
  • Has a blocking version with wait_dequeue
  • Completely "wait-free" (no compare-and-swap loop). Enqueue and dequeue are always O(1) (not counting memory allocation)
  • On x86, the memory barriers compile down to no-ops, meaning enqueue and dequeue are just a simple series of loads and stores (and branches)

Use

Simply drop the readerwriterqueue.h (or readerwritercircularbuffer.h) and atomicops.h files into your source code and include them :-) A modern compiler is required (MSVC2010+, GCC 4.7+, ICC 13+, or any C++11 compliant compiler should work).

Note: If you're using GCC, you really do need GCC 4.7 or above -- 4.6 has a bug that prevents the atomic fence primitives from working correctly.

Example:

using namespace moodycamel;

ReaderWriterQueue<int> q(100);       // Reserve space for at least 100 elements up front

q.enqueue(17);                       // Will allocate memory if the queue is full
bool succeeded = q.try_enqueue(18);  // Will only succeed if the queue has an empty slot (never allocates)
assert(succeeded);

int number;
succeeded = q.try_dequeue(number);  // Returns false if the queue was empty

assert(succeeded && number == 17);

// You can also peek at the front item of the queue (consumer only)
int* front = q.peek();
assert(*front == 18);
succeeded = q.try_dequeue(number);
assert(succeeded && number == 18);
front = q.peek(); 
assert(front == nullptr);           // Returns nullptr if the queue was empty

The blocking version has the exact same API, with the addition of wait_dequeue and wait_dequeue_timed methods:

BlockingReaderWriterQueue<int> q;

std::thread reader([&]() {
    int item;
#if 1
    for (int i = 0; i != 100; ++i) {
        // Fully-blocking:
        q.wait_dequeue(item);
    }
#else
    for (int i = 0; i != 100; ) {
        // Blocking with timeout
        if (q.wait_dequeue_timed(item, std::chrono::milliseconds(5)))
            ++i;
    }
#endif
});
std::thread writer([&]() {
    for (int i = 0; i != 100; ++i) {
        q.enqueue(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
});
writer.join();
reader.join();

assert(q.size_approx() == 0);

Note that wait_dequeue will block indefinitely while the queue is empty; this means care must be taken to only call wait_dequeue if you're sure another element will come along eventually, or if the queue has a static lifetime. This is because destroying the queue while a thread is waiting on it will invoke undefined behaviour.

The blocking circular buffer has a fixed number of slots, but is otherwise quite similar to use:

BlockingReaderWriterCircularBuffer<int> q(1024);  // pass initial capacity

q.try_enqueue(1);
int number;
q.try_dequeue(number);
assert(number == 1);

q.wait_enqueue(123);
q.wait_dequeue(number);
assert(number == 123);

q.wait_dequeue_timed(number, std::chrono::milliseconds(10));

CMake

Using targets in your project

Using this project as a part of an existing CMake project is easy.

In your CMakeLists.txt:

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)

In main.cpp:

#include <readerwriterqueue.h>

int main()
{
    moodycamel::ReaderWriterQueue<int> q(100);
}

Installing into system directories

As an alternative to including the source files in your project directly, you can use CMake to install the library in your system's include directory:

mkdir build
cd build
cmake ..
make install

Then, you can include it from your source code:

#include <readerwriterqueue/readerwriterqueue.h>

Disclaimers

The queue should only be used on platforms where aligned integer and pointer access is atomic; fortunately, that includes all modern processors (e.g. x86/x86-64, ARM, and PowerPC). Not for use with a DEC Alpha processor (which has very weak memory ordering) :-)

Note that it's only been tested on x86(-64); if someone has access to other processors I'd love to run some tests on anything that's not x86-based.

More info

See the LICENSE.md file for the license (simplified BSD).

My blog post introduces the context that led to this code, and may be of interest if you're curious about lock-free programming.