Class thl::core::LockFreeQueue#

template<typename T, std::size_t Capacity>
class LockFreeQueue#

Bounded lock-free multi-producer / multi-consumer queue.

Fixed capacity, no allocation after construction, no locks, no system calls: every operation is a bounded number of atomic loads/stores and CAS attempts, so both ends may run on real-time threads. Implements Dmitry Vyukov’s bounded MPMC scheme: each slot carries a sequence number that tells producers and consumers whether it is theirs to use.

Semantics:

  • try_push() fails when the queue is full (the producer never waits).

  • push_overwrite() makes room by discarding the oldest element instead, for “latest data wins” streams such as meters or log records.

  • try_pop() fails when the queue is empty.

  • Elements from one producer are popped in the order they were pushed; there is no ordering guarantee between different producers.

T must be trivially copyable and default constructible; the queue copies elements by value into pre-allocated slots. Capacity must be a power of two. sizeof(LockFreeQueue) ~= Capacity * (sizeof(T) + 8) + 128, so large queues belong at namespace scope or on the heap, not on the stack.

Objects of this type have trivial destructors; a namespace-scope instance therefore stays valid during static teardown.

Progress and memory ordering (why the code looks the way it does):

  • The queue is lock-free, not wait-free. try_push()/try_pop() retry only when a competing thread has just claimed the same position (the CAS lost, or the local position turned out stale), i.e. every retry means someone else made progress. A slot that is full/empty is reported immediately &#8212; nobody spins waiting for a preempted producer or consumer, which is the property real-time callers rely on.

  • The CAS on the position counter decides ownership of a slot and is relaxed: the counter never carries payload.

  • The per-cell sequence number carries the payload handoff: the owner publishes with a release store after writing the value, the other side reads it with an acquire load before reading the value.

Public Types

using value_type = T#

Public Functions

constexpr LockFreeQueue() noexcept = default#

Constant-initialisable: a namespace-scope instance is ready before any dynamic initialisation runs (declare it constinit to enforce this).

LockFreeQueue(const LockFreeQueue&) = delete#
LockFreeQueue &operator=(const LockFreeQueue&) = delete#
LockFreeQueue(LockFreeQueue&&) = delete#
LockFreeQueue &operator=(LockFreeQueue&&) = delete#
~LockFreeQueue() = default#
inline bool try_push(const T &item) noexcept#

Enqueue a copy of item.

Returns false (and leaves the queue unchanged) if it is full.

inline std::size_t push_overwrite(const T &item) noexcept#

Enqueue a copy of item, discarding the oldest element(s) if the queue is full.

Returns the number of elements discarded. Under heavy contention the attempt is bounded, so in the rare case where room could not be made the item is dropped and Capacity + 1 is returned.

inline bool try_pop(T &out) noexcept#

Dequeue into out.

Returns false (and leaves out untouched) if the queue is empty.

inline std::size_t size_approx() const noexcept#

Approximate number of queued elements.

Exact only when no other thread is pushing or popping concurrently.

inline bool empty_approx() const noexcept#
inline void clear() noexcept#

Discard every queued element.

Not thread-safe: callers must ensure no producer or consumer is active.

Public Static Functions

static inline constexpr std::size_t capacity() noexcept#