dispenso 1.5.1
A library for task parallelism
Loading...
Searching...
No Matches
dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo > Class Template Reference

A lock-free single-producer single-consumer ring buffer with fixed capacity. More...

#include <spsc_ring_buffer.h>

Public Member Functions

 SPSCRingBuffer ()=default
 Constructs an empty ring buffer.
 
 SPSCRingBuffer (const SPSCRingBuffer &)=delete
 Ring buffers are not copyable.
 
 SPSCRingBuffer (SPSCRingBuffer &&)=delete
 Ring buffers are not movable.
 
 ~SPSCRingBuffer ()
 Destroys the ring buffer.
 
bool try_push (T &&item)
 Attempts to push an element into the buffer by moving.
 
bool try_push (const T &item)
 Attempts to push an element into the buffer by copying.
 
template<typename... Args>
bool try_emplace (Args &&... args)
 Attempts to construct an element in-place in the buffer.
 
bool try_pop (T &item)
 Attempts to pop an element from the buffer.
 
OpResult< T > try_pop ()
 Attempts to pop an element from the buffer, returning an optional.
 
bool try_pop_into (T *storage)
 Attempts to pop an element into uninitialized storage.
 
template<typename InputIt >
size_type try_push_batch (InputIt first, InputIt last)
 Attempts to push multiple elements into the buffer.
 
template<typename OutputIt >
size_type try_pop_batch (OutputIt dest, size_type maxCount)
 Attempts to pop multiple elements from the buffer.
 
bool empty () const
 Checks if the buffer is empty.
 
bool full () const
 Checks if the buffer is full.
 
size_type size () const
 Returns the current number of elements in the buffer.
 

Static Public Member Functions

static constexpr size_type capacity () noexcept
 Returns the maximum number of elements the buffer can hold.
 

Detailed Description

template<typename T, size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
class dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >

A lock-free single-producer single-consumer ring buffer with fixed capacity.

This class implements a bounded, lock-free ring buffer optimized for the case where there is exactly one producer thread and exactly one consumer thread. It uses relaxed memory ordering where possible and acquire/release semantics only where necessary for correctness.

The buffer stores elements in a contiguous array, avoiding dynamic memory allocation after construction. The capacity is fixed at compile time via a template parameter.

Unlike std::array-based implementations, this buffer does NOT require the element type to be default-constructible. Elements are constructed in-place when pushed and destroyed when popped.

Template Parameters
TThe type of elements stored in the buffer. Must be move-constructible.
CapacityThe minimum number of elements the buffer can hold. Must be at least 1. Defaults to 16, which provides good cache locality.
RoundUpToPowerOfTwoIf true (default), rounds up the internal buffer size to the next power of two for faster index wrap-around using bitwise AND instead of modulo. This may result in actual capacity being larger than requested. Set to false to use exactly the requested capacity.

Thread Safety

This class is designed for exactly one producer thread and one consumer thread:

  • Only one thread may call try_push() or try_emplace() at any time
  • Only one thread may call try_pop() at any time
  • The producer and consumer may be different threads
  • empty(), full(), and size() may be called from any thread, but provide only a snapshot that may be immediately stale

Memory Ordering

The implementation uses:

  • memory_order_relaxed for local reads of head/tail
  • memory_order_acquire when reading the "other" index (consumer reads head, producer reads tail)
  • memory_order_release when updating head/tail after successful push/pop

Performance Characteristics

  • Push: O(1), wait-free when buffer is not full
  • Pop: O(1), wait-free when buffer is not empty
  • Memory: sizeof(T) * (actual capacity + 1) + 2 cache lines for head/tail indices
  • When RoundUpToPowerOfTwo is true (default), index wrap-around uses fast bitwise AND

Example Usage

// Default: rounds up to power of two for performance
dispenso::SPSCRingBuffer<int, 100> buffer; // actual capacity is 127 (128 - 1)
// Exact capacity mode (no rounding)
dispenso::SPSCRingBuffer<int, 100, false> exact; // actual capacity is 100
// Producer thread
if (buffer.try_push(42)) {
// Success
}
// Consumer thread
int value;
if (buffer.try_pop(value)) {
// Use value
}
A lock-free single-producer single-consumer ring buffer with fixed capacity.
bool try_push(T &&item)
Attempts to push an element into the buffer by moving.
bool try_pop(T &item)
Attempts to pop an element from the buffer.
See also
https://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue for background on lock-free queue algorithms

Definition at line 111 of file spsc_ring_buffer.h.

Constructor & Destructor Documentation

◆ SPSCRingBuffer() [1/3]

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::SPSCRingBuffer ( )
default

Constructs an empty ring buffer.

The buffer is initialized with no elements. The internal storage is uninitialized - elements are only constructed when pushed.

Note
Construction is not thread-safe. Ensure the buffer is fully constructed before any thread accesses it.

◆ SPSCRingBuffer() [2/3]

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::SPSCRingBuffer ( const SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo > & )
delete

Ring buffers are not copyable.

Copying a concurrent data structure would require synchronization and could lead to subtle bugs. Use move semantics if you need to transfer ownership.

◆ SPSCRingBuffer() [3/3]

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::SPSCRingBuffer ( SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo > && )
delete

Ring buffers are not movable.

Moving a ring buffer while producer/consumer threads are active would be unsafe. If you need to transfer ownership, ensure all threads have stopped first.

◆ ~SPSCRingBuffer()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::~SPSCRingBuffer ( )
inline

Destroys the ring buffer.

All elements remaining in the buffer are destroyed. Ensure no producer or consumer threads are accessing the buffer when it is destroyed.

Note
Destruction is not thread-safe.

Definition at line 178 of file spsc_ring_buffer.h.

Member Function Documentation

◆ capacity()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
static constexpr size_type dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::capacity ( )
inlinestaticconstexprnoexcept

Returns the maximum number of elements the buffer can hold.

When RoundUpToPowerOfTwo is true (default), this may be larger than the requested Capacity template parameter, as the internal buffer is rounded up to the next power of two for performance.

Returns
The actual maximum capacity of the buffer (kBufferSize - 1).
Note
The actual storage uses capacity() + 1 slots internally to distinguish between empty and full states.

Definition at line 591 of file spsc_ring_buffer.h.

◆ empty()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
bool dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::empty ( ) const
inline

Checks if the buffer is empty.

Returns
true if the buffer contains no elements, false otherwise.
Note
This function provides only a snapshot of the buffer state. The result may be stale by the time it is used, as another thread may have pushed or popped an element.
Safe to call from any thread, but the result is only a hint.

Definition at line 539 of file spsc_ring_buffer.h.

◆ full()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
bool dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::full ( ) const
inline

Checks if the buffer is full.

Returns
true if the buffer has no space for additional elements, false otherwise.
Note
This function provides only a snapshot of the buffer state. The result may be stale by the time it is used, as another thread may have popped an element.
Safe to call from any thread, but the result is only a hint.

Definition at line 554 of file spsc_ring_buffer.h.

◆ size()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
size_type dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::size ( ) const
inline

Returns the current number of elements in the buffer.

Returns
The number of elements currently in the buffer.
Note
This function provides only a snapshot of the buffer state. The result may be stale by the time it is used.
Safe to call from any thread, but the result is only a hint.
The implementation handles wrap-around correctly using modular arithmetic.

Definition at line 572 of file spsc_ring_buffer.h.

◆ try_emplace()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
template<typename... Args>
bool dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_emplace ( Args &&... args)
inline

Attempts to construct an element in-place in the buffer.

If the buffer has space, constructs an element directly in the buffer storage using the provided arguments, avoiding any copy or move operations.

Template Parameters
ArgsThe types of arguments to forward to T's constructor.
Parameters
argsThe arguments to forward to the element constructor.
Returns
true if the element was successfully emplaced, false if the buffer was full.
Note
Only one thread may call this function at any time (single producer).
This operation is wait-free.

Example

if (buffer.try_emplace(42, "hello")) {
// Element constructed in-place
}
bool try_emplace(Args &&... args)
Attempts to construct an element in-place in the buffer.

Definition at line 278 of file spsc_ring_buffer.h.

◆ try_pop() [1/2]

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
OpResult< T > dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_pop ( )
inline

Attempts to pop an element from the buffer, returning an optional.

If the buffer has elements, moves the front element into an OpResult and returns it. If the buffer is empty, returns an empty OpResult.

This provides a cleaner API than try_pop(T&) when default-constructibility of T is not guaranteed or when a more functional style is preferred.

Returns
An OpResult containing the popped element, or an empty OpResult if the buffer was empty.
Note
Only one thread may call this function at any time (single consumer).
This operation is wait-free.
In C++17 and beyond, you may prefer to use std::optional directly.

Example

buffer.try_push("hello");
if (auto result = buffer.try_pop()) {
std::cout << result.value() << std::endl;
}

Definition at line 358 of file spsc_ring_buffer.h.

◆ try_pop() [2/2]

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
bool dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_pop ( T & item)
inline

Attempts to pop an element from the buffer.

If the buffer has elements, moves the front element into the output parameter and returns true. If the buffer is empty, returns false and leaves the output parameter unchanged.

Parameters
[out]itemThe location to move the popped element to.
Returns
true if an element was successfully popped, false if the buffer was empty.
Note
Only one thread may call this function at any time (single consumer).
This operation is wait-free.

Example

buffer.try_push(42);
int value;
if (buffer.try_pop(value)) {
assert(value == 42);
}

Definition at line 317 of file spsc_ring_buffer.h.

◆ try_pop_batch()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
template<typename OutputIt >
size_type dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_pop_batch ( OutputIt dest,
size_type maxCount )
inline

Attempts to pop multiple elements from the buffer.

Pops up to maxCount elements from the buffer into the output iterator in a single atomic head update. This reduces atomic operation overhead when consuming multiple items.

Template Parameters
OutputItOutput iterator type. Must be assignable from T.
Parameters
destOutput iterator to write popped elements to.
maxCountMaximum number of elements to pop.
Returns
The number of elements successfully popped. May be less than maxCount if the buffer has fewer elements.
Note
Only one thread may call this function at any time (single consumer).
Elements are moved to the output range.

Example

// ... push some items ...
std::vector<int> items(4);
size_t popped = buffer.try_pop_batch(items.begin(), 4);
// First 'popped' elements of items contain the popped values
size_type try_pop_batch(OutputIt dest, size_type maxCount)
Attempts to pop multiple elements from the buffer.

Definition at line 496 of file spsc_ring_buffer.h.

◆ try_pop_into()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
bool dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_pop_into ( T * storage)
inline

Attempts to pop an element into uninitialized storage.

Similar to try_pop, but uses placement new to construct the element into the provided storage. This is useful when T is not default-constructible.

Parameters
[out]storagePointer to uninitialized storage where the element will be move-constructed. Must have proper alignment for T.
Returns
true if an element was successfully popped, false if the buffer was empty.
Note
Only one thread may call this function at any time (single consumer).
The caller is responsible for eventually destroying the constructed object.
This operation is wait-free.

Example

alignas(NonDefaultConstructible) char storage[sizeof(NonDefaultConstructible)];
if (buffer.try_pop_into(reinterpret_cast<NonDefaultConstructible*>(storage))) {
auto* ptr = reinterpret_cast<NonDefaultConstructible*>(storage);
// use *ptr
ptr->~NonDefaultConstructible();
}
bool try_pop_into(T *storage)
Attempts to pop an element into uninitialized storage.

Definition at line 398 of file spsc_ring_buffer.h.

◆ try_push() [1/2]

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
bool dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_push ( const T & item)
inline

Attempts to push an element into the buffer by copying.

If the buffer has space, the element is copied into the buffer and the function returns true. If the buffer is full, the function returns false.

Parameters
itemThe element to push (will be copied).
Returns
true if the element was successfully pushed, false if the buffer was full.
Note
Only one thread may call this function at any time (single producer).
This operation is wait-free.
Prefer try_push(T&&) when the source element is no longer needed.

Definition at line 241 of file spsc_ring_buffer.h.

◆ try_push() [2/2]

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
bool dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_push ( T && item)
inline

Attempts to push an element into the buffer by moving.

If the buffer has space, the element is moved into the buffer and the function returns true. If the buffer is full, the function returns false and the element is unchanged.

Parameters
itemThe element to push (will be moved from on success).
Returns
true if the element was successfully pushed, false if the buffer was full.
Note
Only one thread may call this function at any time (single producer).
This operation is wait-free.

Example

std::string msg = "hello";
if (buffer.try_push(std::move(msg))) {
// msg is now empty (moved from)
} else {
// msg is unchanged, buffer was full
}

Definition at line 212 of file spsc_ring_buffer.h.

◆ try_push_batch()

template<typename T , size_t Capacity = 16, bool RoundUpToPowerOfTwo = true>
template<typename InputIt >
size_type dispenso::SPSCRingBuffer< T, Capacity, RoundUpToPowerOfTwo >::try_push_batch ( InputIt first,
InputIt last )
inline

Attempts to push multiple elements into the buffer.

Pushes as many elements as possible from the range [first, last) into the buffer in a single atomic tail update. This reduces atomic operation overhead when pushing multiple items.

Template Parameters
InputItInput iterator type. Must dereference to a type convertible to T.
Parameters
firstIterator to the first element to push.
lastIterator past the last element to push.
Returns
The number of elements successfully pushed. May be less than the range size if the buffer becomes full.
Note
Only one thread may call this function at any time (single producer).
Elements are moved from the input range.

Example

std::vector<int> items = {1, 2, 3, 4, 5};
size_t pushed = buffer.try_push_batch(items.begin(), items.end());
// pushed contains the number of items successfully added
size_type try_push_batch(InputIt first, InputIt last)
Attempts to push multiple elements into the buffer.

Definition at line 438 of file spsc_ring_buffer.h.


The documentation for this class was generated from the following file: