#pragma once #include #include namespace lockfree { template class BoundedSpscQueue { public: static constexpr size_t size = N; BoundedSpscQueue() = default; BoundedSpscQueue(const BoundedSpscQueue&) = delete; BoundedSpscQueue(BoundedSpscQueue&&) = delete; BoundedSpscQueue& operator=(const BoundedSpscQueue&) = delete; bool push(const T& item) { // load the current tail // [] [] [1] [2] [3] [4] [5] [$] [] // H T auto t = tail.load(std::memory_order_relaxed); // what will next tail be after we push // [] [] [1] [2] [3] [4] [5] [$] [ ] // H T T' auto next = increment(t); // check if queue is full and do nothing if it is // [3] [4] [5] [6] [7] [8] [$] [ 1 ] [2] // T T'H if (next == head.load(std::memory_order_acquire)) return false; // insert the item into the empty spot // [] [] [1] [2] [3] [4] [5] [ ] [] // H T T' items[t] = item; // release the tail to the consumer (serialization point) // [] [] [1] [2] [3] [4] [5] [ $ ] [] // H T T' tail.store(next, std::memory_order_release); return true; } bool pop(T& item) { // [] [] [1] [2] [3] [4] [5] [$] [] // H T auto h = head.load(std::memory_order_relaxed); // [] [] [] [] [ $ ] [] [] [] [] // H T if (h == tail.load(std::memory_order_acquire)) return false; // move an item from the queue item = std::move(items[h]); // serialization point wrt producer // [] [] [] [2] [3] [4] [5] [$] [] // H T head.store(increment(h), std::memory_order_release); return true; } private: static constexpr size_t capacity = N + 1; std::array items; std::atomic head{0}, tail{0}; size_t increment(size_t idx) const { return (idx + 1) % capacity; } }; }