11#pragma once
22
3+ #include < atomic>
34#include < cstddef>
45#include < memory>
5- #include < mutex>
66#include < stdexcept>
77
88#include " internal/common.h"
@@ -12,21 +12,26 @@ namespace RingBuffer { // interface
1212template <typename T>
1313class alignas (Common::CACHELINE_SIZE) MPMCRingBuffer {
1414 private:
15- std::unique_ptr<T[], Common::AlignedDeleter> buffer;
15+ struct Slot {
16+ alignas (Common::CACHELINE_SIZE) std::atomic<size_t > seq;
17+ alignas (Common::CACHELINE_SIZE) T data;
18+ };
19+
20+ std::unique_ptr<Slot[], Common::AlignedDeleter> buffer;
1621 const size_t capacity;
22+ const std::size_t alignment;
1723
18- alignas (Common::CACHELINE_SIZE) mutable std::mutex mtx;
19- alignas (Common::CACHELINE_SIZE) size_t head = 0 ;
20- alignas (Common::CACHELINE_SIZE) size_t tail = 0 ;
24+ alignas (Common::CACHELINE_SIZE) std::atomic<size_t > tail = 0 ;
25+ alignas (Common::CACHELINE_SIZE) std::atomic<size_t > head = 0 ;
2126
2227 public:
2328 explicit MPMCRingBuffer (size_t cap,
2429 std::size_t align = Common::CACHELINE_SIZE);
25- bool tryPush (const T& value);
26- bool tryPush (T&& value);
30+ bool tryPush (const T & value);
31+ bool tryPush (T && value);
2732 template <typename ... Args>
28- bool tryEmplace (Args&&... args);
29- bool tryPop (T& value);
33+ bool tryEmplace (Args &&...args );
34+ bool tryPop (T & value);
3035};
3136
3237} // namespace RingBuffer
@@ -35,56 +40,102 @@ namespace RingBuffer { // implementation
3540
3641template <typename T>
3742MPMCRingBuffer<T>::MPMCRingBuffer(size_t cap, std::size_t align)
38- : capacity(cap + 1 ), buffer(nullptr , Common::AlignedDeleter{align}) {
39- if (cap == 0 )
40- throw std::invalid_argument (
41- " capacity of ring buffer must be greater than 0" );
42-
43- T* ptr = static_cast <T*>(
44- ::operator new [](capacity * sizeof (T), std::align_val_t (align)));
45- buffer.reset (ptr);
43+ : capacity(cap),
44+ alignment (align),
45+ buffer(nullptr , Common::AlignedDeleter{align}) {
46+ if (cap == 0 ) throw std::invalid_argument (" Capacity must be greater than 0" );
47+
48+ Slot *raw = static_cast <Slot *>(
49+ ::operator new [](capacity * sizeof (Slot), std::align_val_t (align)));
50+ for (size_t i = 0 ; i < capacity; ++i) {
51+ new (&raw[i]) Slot{.seq = i}; // placement new
52+ }
53+ buffer.reset (raw);
4654}
4755
4856template <typename T>
49- bool MPMCRingBuffer<T>::tryPush(const T& value) {
50- std::lock_guard<std::mutex> lock (mtx);
51- if (((tail + 1 ) % capacity) == head) return false ;
52- buffer[tail] = value;
53- tail = (tail + 1 ) % capacity;
54- return true ;
57+ bool MPMCRingBuffer<T>::tryPush(const T &value) {
58+ size_t pos = tail.load (std::memory_order_relaxed);
59+
60+ while (true ) {
61+ Slot &slot = buffer[pos % capacity];
62+ size_t expected = pos;
63+
64+ if (slot.seq .load (std::memory_order_acquire) != expected) {
65+ return false ;
66+ }
67+
68+ if (tail.compare_exchange_weak (pos, pos + 1 , std::memory_order_relaxed)) {
69+ slot.data = value;
70+ slot.seq .store (expected + 1 , std::memory_order_release);
71+ return true ;
72+ }
73+ }
5574}
5675
5776template <typename T>
58- bool MPMCRingBuffer<T>::tryPush(T&& value) {
59- std::lock_guard<std::mutex> lock (mtx);
60- if (((tail + 1 ) % capacity) == head) return false ;
61- buffer[tail] = std::move (value);
62- tail = (tail + 1 ) % capacity;
63- return true ;
77+ bool MPMCRingBuffer<T>::tryPush(T &&value) {
78+ size_t pos = tail.load (std::memory_order_relaxed);
79+
80+ while (true ) {
81+ Slot &slot = buffer[pos % capacity];
82+ size_t expected = pos;
83+
84+ if (slot.seq .load (std::memory_order_acquire) != expected) {
85+ return false ;
86+ }
87+
88+ if (tail.compare_exchange_weak (pos, pos + 1 , std::memory_order_relaxed)) {
89+ slot.data = std::move (value);
90+ slot.seq .store (expected + 1 , std::memory_order_release);
91+ return true ;
92+ }
93+ }
6494}
6595
6696template <typename T>
6797template <typename ... Args>
68- bool MPMCRingBuffer<T>::tryEmplace(Args&&... args) {
69- std::lock_guard<std::mutex> lock (mtx);
70- if (((tail + 1 ) % capacity) == head) return false ;
71- new (&buffer[tail]) T (std::forward<Args>(args)...);
72- tail = (tail + 1 ) % capacity;
73- return true ;
98+ bool MPMCRingBuffer<T>::tryEmplace(Args &&...args) {
99+ size_t pos = tail.load (std::memory_order_relaxed);
100+
101+ while (true ) {
102+ Slot &slot = buffer[pos % capacity];
103+ size_t expected = pos;
104+
105+ if (slot.seq .load (std::memory_order_acquire) != expected) {
106+ return false ;
107+ }
108+
109+ if (tail.compare_exchange_weak (pos, pos + 1 , std::memory_order_relaxed)) {
110+ new (&slot.data ) T (std::forward<Args>(args)...);
111+ slot.seq .store (expected + 1 , std::memory_order_release);
112+ return true ;
113+ }
114+ }
74115}
75116
76117template <typename T>
77- bool MPMCRingBuffer<T>::tryPop(T& value) {
78- std::lock_guard<std::mutex> lock (mtx);
79- if (head == tail) return false ;
80-
81- T* elem = &buffer[head];
82- value.~T ();
83- new (&value) T (std::move (*elem));
84- elem->~T ();
85-
86- head = (head + 1 ) % capacity;
87- return true ;
118+ bool MPMCRingBuffer<T>::tryPop(T &value) {
119+ size_t pos = head.load (std::memory_order_relaxed);
120+
121+ while (true ) {
122+ Slot &slot = buffer[pos % capacity];
123+ size_t expected = pos + 1 ;
124+
125+ if (slot.seq .load (std::memory_order_acquire) != expected) {
126+ return false ;
127+ }
128+
129+ if (head.compare_exchange_weak (pos, pos + 1 , std::memory_order_relaxed)) {
130+ T *elem = &(slot.data );
131+ value.~T ();
132+ new (&value) T (std::move (*elem));
133+ elem->~T ();
134+
135+ slot.seq .store (pos + capacity, std::memory_order_release);
136+ return true ;
137+ }
138+ }
88139}
89140
90141} // namespace RingBuffer
0 commit comments