88 #if defined(__linux__)
89 #define VLINK_NO_INSTRUMENT __attribute__((no_instrument_function))
91 #define VLINK_NO_INSTRUMENT
116 enum Behavior : uint8_t { kNoBehavior = 0, kConditionBehavior = 1 };
166 bool wait_not_empty(std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) noexcept
VLINK_NO_INSTRUMENT;
177 bool wait_not_full(std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) noexcept
VLINK_NO_INSTRUMENT;
191 static constexpr std::memory_order kMemoryOrderAcquire = std::memory_order_acquire;
192 static constexpr std::memory_order kMemoryOrderRelease = std::memory_order_release;
193 static constexpr std::memory_order kMemoryOrderRelaxed = std::memory_order_relaxed;
195 static constexpr
size_t kInterferenceSize = 64U;
196 static constexpr
size_t kFirstSpinTimes = 32U;
202 [[noreturn]] static
void throw_mpmc_invalid_capacity();
204 [[noreturn]] static
void throw_mpmc_alignment_failure();
206 [[nodiscard]] constexpr
size_t idx(
size_t i) const noexcept {
return i % capacity_; }
208 [[nodiscard]] constexpr
size_t turn(
size_t i)
const noexcept {
return i / capacity_; }
210 alignas(kInterferenceSize) std::atomic<size_t> head_{0U};
212 void* chunk_storage_{
nullptr};
217 alignas(kInterferenceSize) std::atomic<size_t> tail_{0U};
223 std::atomic_bool value{
false};
224 char padding[kInterferenceSize -
sizeof(std::atomic_bool)];
238 template <
typename T>
278 template <
Behavior BehaviorT = kNoBehavior,
typename... Args>
294 template <
Behavior BehaviorT = kNoBehavior,
typename... Args>
307 template <Behavior BehaviorT = kNoBehavior,
typename P>
318 template <Behavior BehaviorT = kNoBehavior,
typename P>
333 template <Behavior BehaviorT = kNoBehavior>
346 template <Behavior BehaviorT = kNoBehavior>
350 #if defined(__cpp_aligned_new)
351 template <
typename ChunkT>
352 using AlignedAllocator = std::allocator<ChunkT>;
354 template <
typename ChunkT>
355 struct AlignedAllocator {
356 using value_type = ChunkT;
358 ChunkT* allocate(
size_t n) {
359 if VUNLIKELY (n > std::numeric_limits<size_t>::max() /
sizeof(ChunkT)) {
360 throw std::bad_array_new_length();
364 auto* p =
static_cast<ChunkT*
>(_aligned_malloc(
sizeof(ChunkT) * n,
alignof(ChunkT)));
367 throw std::bad_alloc();
372 if VUNLIKELY (posix_memalign(
reinterpret_cast<void**
>(&p),
alignof(ChunkT),
sizeof(ChunkT) * n) != 0) {
373 throw std::bad_alloc();
380 void deallocate(ChunkT* p,
size_t) {
392 if ((turn.load(kMemoryOrderAcquire) & 1U) != 0U) {
397 template <
typename... Args>
398 void construct(Args&&... args) noexcept {
399 new (storage.data()) T(std::forward<Args>(args)...);
402 void destroy() noexcept { std::launder(
reinterpret_cast<T*
>(storage.data()))->~T(); }
404 [[nodiscard]] T&& move() noexcept {
return std::move(*std::launder(
reinterpret_cast<T*
>(storage.data()))); }
406 alignas(kInterferenceSize) std::atomic<size_t> turn{0U};
407 alignas(
alignof(T)) std::array<uint8_t,
sizeof(T)> storage;
410 [[nodiscard]] Chunk* chunks() noexcept {
return static_cast<Chunk*
>(chunk_storage_); }
412 [[nodiscard]]
const Chunk* chunks()
const noexcept {
return static_cast<const Chunk*
>(chunk_storage_); }
421 template <
typename T>
424 "Slot must be aligned to cache line boundary to prevent false sharing");
426 "Slot size must be a multiple of cache line size to prevent "
427 "false sharing between adjacent slots");
429 "Queue size must be a multiple of cache line size to "
430 "prevent false sharing between adjacent queues");
432 AlignedAllocator<Chunk> allocator;
433 Chunk* raw = allocator.allocate(
capacity_ + 1);
435 if VUNLIKELY (
reinterpret_cast<size_t>(raw) %
alignof(Chunk) != 0U) {
436 allocator.deallocate(raw,
capacity_ + 1);
442 for (
size_t i = 0U; i <
capacity_; ++i) {
443 new (&raw[i]) Chunk();
447 template <
typename T>
449 Chunk* raw = chunks();
451 for (
size_t i = 0U; i < capacity_; ++i) {
455 AlignedAllocator<Chunk> allocator;
456 allocator.deallocate(raw, capacity_ + 1);
459 template <
typename T>
460 template <typename MpmcQueue<T>::Behavior BehaviorT,
typename... Args>
462 if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
466 if constexpr (BehaviorT == kConditionBehavior) {
467 wait_not_full(std::chrono::milliseconds(0));
469 if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
474 const auto head = head_.fetch_add(1U, std::memory_order_seq_cst);
475 auto& chunk = chunks()[idx(head)];
479 while (turn(head) * 2U != chunk.turn.load(kMemoryOrderAcquire)) {
480 if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
484 if (++spin < kFirstSpinTimes) {
490 chunk.construct(std::forward<Args>(args)...);
491 chunk.turn.store((turn(head) * 2U) + 1U, kMemoryOrderRelease);
493 if constexpr (BehaviorT == kConditionBehavior) {
494 std::lock_guard lock(cv_mtx_);
495 cv_not_empty_.notify_one();
499 template <
typename T>
500 template <typename MpmcQueue<T>::Behavior BehaviorT,
typename... Args>
502 if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
506 auto head = head_.load(kMemoryOrderAcquire);
509 auto& chunk = chunks()[idx(head)];
511 if VLIKELY (turn(head) * 2U == chunk.turn.load(kMemoryOrderAcquire)) {
512 if (head_.compare_exchange_strong(head, head + 1U, std::memory_order_seq_cst, std::memory_order_seq_cst)) {
513 chunk.construct(std::forward<Args>(args)...);
514 chunk.turn.store((turn(head) * 2U) + 1U, kMemoryOrderRelease);
516 if constexpr (BehaviorT == kConditionBehavior) {
517 std::lock_guard lock(cv_mtx_);
518 cv_not_empty_.notify_one();
524 const auto prev_head = head;
525 head = head_.load(kMemoryOrderAcquire);
534 template <
typename T>
535 template <typename MpmcQueue<T>::Behavior BehaviorT,
typename P>
537 emplace<BehaviorT>(std::forward<P>(v));
540 template <
typename T>
541 template <typename MpmcQueue<T>::Behavior BehaviorT,
typename P>
543 return try_emplace<BehaviorT>(std::forward<P>(v));
546 template <
typename T>
547 template <typename MpmcQueue<T>::Behavior BehaviorT>
549 auto const tail = tail_.fetch_add(1U, std::memory_order_seq_cst);
550 auto& chunk = chunks()[idx(tail)];
554 while (turn(tail) * 2U + 1U != chunk.turn.load(kMemoryOrderAcquire)) {
556 if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
560 if (++spin < kFirstSpinTimes) {
569 chunk.turn.store((turn(tail) * 2U) + 2U, kMemoryOrderRelease);
571 if constexpr (BehaviorT == kConditionBehavior) {
572 std::lock_guard lock(cv_mtx_);
573 cv_not_full_.notify_one();
577 template <
typename T>
578 template <typename MpmcQueue<T>::Behavior BehaviorT>
580 if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
584 auto tail = tail_.load(kMemoryOrderAcquire);
587 auto& chunk = chunks()[idx(tail)];
589 if VLIKELY (turn(tail) * 2U + 1U == chunk.turn.load(kMemoryOrderAcquire)) {
590 if (tail_.compare_exchange_strong(tail, tail + 1U, std::memory_order_seq_cst, std::memory_order_seq_cst)) {
593 chunk.turn.store((turn(tail) * 2U) + 2U, kMemoryOrderRelease);
595 if constexpr (BehaviorT == kConditionBehavior) {
596 std::lock_guard lock(cv_mtx_);
597 cv_not_full_.notify_one();
603 const auto prev_tail = tail;
604 tail = tail_.load(kMemoryOrderAcquire);
611 if VUNLIKELY (quit_flag_.value.load(kMemoryOrderAcquire)) {
Non-template base class that owns every element-type-independent piece of state.
Definition: mpmc_queue.h:106
static constexpr size_t kInterferenceSize
Definition: mpmc_queue.h:195
static void throw_mpmc_alignment_failure()
std::mutex cv_mtx_
Definition: mpmc_queue.h:215
void * chunk_storage_
Definition: mpmc_queue.h:212
Behavior
Per-call notification behaviour selector.
Definition: mpmc_queue.h:116
ConditionVariable cv_not_empty_
Definition: mpmc_queue.h:219
size_t capacity() const noexcept VLINK_NO_INSTRUMENT
Returns the fixed capacity chosen at construction.
size_t capacity_
Definition: mpmc_queue.h:213
ConditionVariable cv_not_full_
Definition: mpmc_queue.h:220
constexpr size_t turn(size_t i) const noexcept
Definition: mpmc_queue.h:208
Fixed-capacity lock-free MPMC ring buffer over T.
Definition: mpmc_queue.h:239
void pop(T &v) noexcept VLINK_NO_INSTRUMENT
Pops a value by move; blocks until an element is available.
Definition: mpmc_queue.h:548
bool try_push(P &&v) noexcept VLINK_NO_INSTRUMENT
Non-blocking push wrapper around try_emplace.
Definition: mpmc_queue.h:542
~MpmcQueue() noexcept VLINK_NO_INSTRUMENT
Destructor; destroys still-occupied slots and releases the chunk array.
Definition: mpmc_queue.h:448
MpmcQueue(size_t capacity) VLINK_NO_INSTRUMENT
Constructs a queue with the given fixed capacity.
Definition: mpmc_queue.h:422
void push(P &&v) noexcept VLINK_NO_INSTRUMENT
Pushes a perfect-forwarded value, blocking until a slot is available.
Definition: mpmc_queue.h:536
bool try_pop(T &v) noexcept VLINK_NO_INSTRUMENT
Non-blocking pop; returns false when the queue is empty.
Definition: mpmc_queue.h:579
void emplace(Args &&... args) noexcept VLINK_NO_INSTRUMENT
In-place constructs an element and blocks until a slot is available.
Definition: mpmc_queue.h:461
bool try_emplace(Args &&... args) noexcept VLINK_NO_INSTRUMENT
Non-blocking in-place construction.
Definition: mpmc_queue.h:501
Monotonic-clock condition variable replacement immune to system clock jumps.
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81
#define VLIKELY(...)
Short alias for VLINK_LIKELY.
Definition: macros.h:284
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174
#define VLINK_NO_INSTRUMENT
Definition: mpmc_queue.h:91
VLINK_EXPORT void yield_cpu() noexcept
Emits the most efficient CPU pause/yield hint for the host ISA.
Definition: utils.h:433
std::condition_variable ConditionVariable
Definition: condition_variable.h:605
Definition: mpmc_queue.h:222
Portable host-system utility surface used across the VLink runtime.