File spsc.hpp¶
File List > include > loon > spsc.hpp
Go to the documentation of this file
// Copyright (c) 2026 Jorge Suarez-Rivaya
// SPDX-License-Identifier: MIT
#pragma once
#include <atomic>
#include <cstddef>
#include <new>
#ifndef CACHE_LINE_SIZE
#if defined(__cpp_lib_hardware_interference_size)
#define CACHE_LINE_SIZE std::hardware_destructive_interference_size
#else
#define CACHE_LINE_SIZE 64
#endif
#endif
namespace loon {
template <typename T, size_t N>
class SpscQueue {
static_assert(N > 0, "SpscQueue capacity must be greater than 0");
static_assert(std::atomic<size_t>::is_always_lock_free, "SpscQueue requires lock-free atomics");
public:
[[nodiscard]] bool push(const T& value) {
auto write = write_idx_.load(std::memory_order_relaxed);
if (write - read_idx_cache_ == N) {
read_idx_cache_ = read_idx_.load(std::memory_order_acquire);
if (write - read_idx_cache_ == N)
return false;
}
data_[write % N] = value;
write_idx_.store(write + 1, std::memory_order_release);
return true;
}
[[nodiscard]] bool pop(T& value) {
auto read = read_idx_.load(std::memory_order_relaxed);
if (write_idx_cache_ == read) {
write_idx_cache_ = write_idx_.load(std::memory_order_acquire);
if (write_idx_cache_ == read)
return false;
}
value = data_[read % N];
read_idx_.store(read + 1, std::memory_order_release);
return true;
}
size_t capacity() const { return N; }
[[nodiscard]] bool empty() const {
return write_idx_.load(std::memory_order_acquire) == read_idx_.load(std::memory_order_acquire);
}
[[nodiscard]] bool full() const {
return write_idx_.load(std::memory_order_acquire) - read_idx_.load(std::memory_order_acquire) ==
N;
}
private:
T data_[N];
alignas(CACHE_LINE_SIZE) std::atomic<size_t> write_idx_{0}; // Producer-owned
alignas(CACHE_LINE_SIZE) size_t write_idx_cache_{0}; // Producer's cache of read_idx_
alignas(CACHE_LINE_SIZE) std::atomic<size_t> read_idx_{0}; // Consumer-owned
alignas(CACHE_LINE_SIZE) size_t read_idx_cache_{0}; // Consumer's cache of write_idx_
};
} // namespace loon