libxr  1.0
Want to be the best embedded framework
Loading...
Searching...
No Matches
spsc_queue_base.hpp
1#pragma once
2
3#include <algorithm>
4#include <atomic>
5#include <cstddef>
6#include <limits>
7#include <new>
8
9#include "libxr_def.hpp"
10#include "libxr_mem.hpp"
11
12namespace LibXR
13{
27class alignas(LibXR::CONCURRENCY_ALIGNMENT) SPSCQueueBase
28{
29 public:
30 using IndexType = size_t;
31
37 SPSCQueueBase(size_t element_size, size_t capacity)
38 : element_size_(element_size),
39 capacity_(capacity),
40 payload_alloc_align_(alignof(std::max_align_t)),
41 payload_stride_(ComputeStride(element_size, alignof(std::byte))),
42 payloads_(nullptr),
43 head_(0),
44 tail_(0)
45 {
46 InitStorage();
47 }
48
56 SPSCQueueBase(size_t element_size, size_t element_align, size_t capacity)
57 : element_size_(element_size),
58 capacity_(capacity),
59 payload_alloc_align_(alignof(std::max_align_t)),
60 payload_stride_(ComputeStride(element_size, element_align)),
61 payloads_(nullptr),
62 head_(0),
63 tail_(0)
64 {
65 InitStorage();
66 }
67
68 private:
69 void InitStorage()
70 {
71 ASSERT(element_size_ > 0);
72 ASSERT(payload_alloc_align_ > 0);
73 ASSERT(capacity_ > 0);
74 ASSERT(capacity_ <= std::numeric_limits<size_t>::max() - 1);
75 ASSERT((payload_alloc_align_ & (payload_alloc_align_ - 1)) == 0);
76
77 const size_t payload_bytes = MultiplyChecked(payload_stride_, RingCapacity());
78 payloads_ = static_cast<std::byte*>(
79 ::operator new[](payload_bytes, std::align_val_t(payload_alloc_align_)));
80 }
81
82 public:
87 {
88 ::operator delete[](payloads_, std::align_val_t(payload_alloc_align_));
89 }
90
99 ErrorCode PushBytes(const void* value)
100 {
101 if (value == nullptr)
102 {
103 return ErrorCode::PTR_NULL;
104 }
105
106 const auto current_tail = tail_.load(std::memory_order_relaxed);
107 const auto next_tail = Increment(current_tail);
108
109 if (next_tail == head_.load(std::memory_order_acquire))
110 {
111 return ErrorCode::FULL;
112 }
113
114 LibXR::Memory::FastCopy(PayloadPtr(current_tail), value, element_size_);
115 tail_.store(next_tail, std::memory_order_release);
116 return ErrorCode::OK;
117 }
118
128 ErrorCode PopBytes(void* value = nullptr)
129 {
130 const auto current_head = head_.load(std::memory_order_relaxed);
131
132 if (current_head == tail_.load(std::memory_order_acquire))
133 {
134 return ErrorCode::EMPTY;
135 }
136
137 if (value != nullptr)
138 {
139 LibXR::Memory::FastCopy(value, PayloadPtr(current_head), element_size_);
140 }
141
142 head_.store(Increment(current_head), std::memory_order_release);
143 return ErrorCode::OK;
144 }
145
155 ErrorCode PeekBytes(void* value)
156 {
157 if (value == nullptr)
158 {
159 return ErrorCode::PTR_NULL;
160 }
161
162 const auto current_head = head_.load(std::memory_order_relaxed);
163 if (current_head == tail_.load(std::memory_order_acquire))
164 {
165 return ErrorCode::EMPTY;
166 }
167
168 LibXR::Memory::FastCopy(value, PayloadPtr(current_head), element_size_);
169 return ErrorCode::OK;
170 }
171
180 ErrorCode PushBatchBytes(const void* data, size_t count)
181 {
182 if (count == 0U)
183 {
184 return ErrorCode::OK;
185 }
186 if (data == nullptr)
187 {
188 return ErrorCode::PTR_NULL;
189 }
190
191 const auto current_tail = tail_.load(std::memory_order_relaxed);
192 const auto current_head = head_.load(std::memory_order_acquire);
193 const size_t capacity = RingCapacity();
194 const size_t free_space = (current_tail >= current_head)
195 ? (capacity - (current_tail - current_head) - 1)
196 : (current_head - current_tail - 1);
197
198 if (free_space < count)
199 {
200 return ErrorCode::FULL;
201 }
202
203 const auto* src = static_cast<const std::byte*>(data);
204 for (size_t index = 0; index < count; ++index)
205 {
206 LibXR::Memory::FastCopy(PayloadPtr((current_tail + index) % capacity),
207 src + index * element_size_, element_size_);
208 }
209
210 tail_.store((current_tail + count) % capacity, std::memory_order_release);
211 return ErrorCode::OK;
212 }
213
229 template <typename Writer>
230 ErrorCode PushBytesWithWriter(size_t count, Writer&& writer)
231 {
232 if (count == 0U)
233 {
234 return ErrorCode::OK;
235 }
236
237 const auto current_tail = tail_.load(std::memory_order_relaxed);
238 const auto current_head = head_.load(std::memory_order_acquire);
239 const size_t capacity = RingCapacity();
240 const size_t free_space = (current_tail >= current_head)
241 ? (capacity - (current_tail - current_head) - 1)
242 : (current_head - current_tail - 1);
243
244 if (free_space < count)
245 {
246 return ErrorCode::FULL;
247 }
248
249 const size_t first_chunk = std::min(count, capacity - current_tail);
250 Writer& writer_ref = writer;
251 const ErrorCode first_ec = writer_ref(PayloadPtr(current_tail), first_chunk);
252 if (first_ec != ErrorCode::OK)
253 {
254 return first_ec;
255 }
256
257 if (count > first_chunk)
258 {
259 const ErrorCode second_ec = writer_ref(PayloadPtr(0), count - first_chunk);
260 if (second_ec != ErrorCode::OK)
261 {
262 return second_ec;
263 }
264 }
265
266 tail_.store((current_tail + count) % capacity, std::memory_order_release);
267 return ErrorCode::OK;
268 }
269
279 ErrorCode PopBatchBytes(void* data, size_t count)
280 {
281 if (count == 0U)
282 {
283 return ErrorCode::OK;
284 }
285
286 const auto current_head = head_.load(std::memory_order_relaxed);
287 const auto current_tail = tail_.load(std::memory_order_acquire);
288 const size_t capacity = RingCapacity();
289 const size_t available = (current_tail >= current_head)
290 ? (current_tail - current_head)
291 : (capacity - current_head + current_tail);
292
293 if (available < count)
294 {
295 return ErrorCode::EMPTY;
296 }
297
298 auto* dst = static_cast<std::byte*>(data);
299 if (dst != nullptr)
300 {
301 for (size_t index = 0; index < count; ++index)
302 {
304 PayloadPtr((current_head + index) % capacity),
306 }
307 }
308
309 head_.store((current_head + count) % capacity, std::memory_order_release);
310 return ErrorCode::OK;
311 }
312
329 template <typename Reader>
330 ErrorCode PopBytesWithReader(size_t count, Reader&& reader)
331 {
332 if (count == 0U)
333 {
334 return ErrorCode::OK;
335 }
336
337 const auto current_head = head_.load(std::memory_order_relaxed);
338 const auto current_tail = tail_.load(std::memory_order_acquire);
339 const size_t capacity = RingCapacity();
340 const size_t available = (current_tail >= current_head)
341 ? (current_tail - current_head)
342 : (capacity - current_head + current_tail);
343
344 if (available < count)
345 {
346 return ErrorCode::EMPTY;
347 }
348
349 const size_t first_chunk = std::min(count, capacity - current_head);
350 Reader& reader_ref = reader;
351 const ErrorCode first_ec = reader_ref(PayloadPtr(current_head), first_chunk);
352 if (first_ec != ErrorCode::OK)
353 {
354 return first_ec;
355 }
356
357 if (count > first_chunk)
358 {
359 const ErrorCode second_ec = reader_ref(PayloadPtr(0), count - first_chunk);
360 if (second_ec != ErrorCode::OK)
361 {
362 return second_ec;
363 }
364 }
365
366 head_.store((current_head + count) % capacity, std::memory_order_release);
367 return ErrorCode::OK;
368 }
369
379 ErrorCode PeekBatchBytes(void* data, size_t count)
380 {
381 if (count == 0U)
382 {
383 return ErrorCode::OK;
384 }
385 if (data == nullptr)
386 {
387 return ErrorCode::PTR_NULL;
388 }
389
390 const auto current_head = head_.load(std::memory_order_relaxed);
391 const auto current_tail = tail_.load(std::memory_order_acquire);
392 const size_t capacity = RingCapacity();
393 const size_t available = (current_tail >= current_head)
394 ? (current_tail - current_head)
395 : (capacity - current_head + current_tail);
396
397 if (available < count)
398 {
399 return ErrorCode::EMPTY;
400 }
401
402 auto* dst = static_cast<std::byte*>(data);
403 for (size_t index = 0; index < count; ++index)
404 {
406 PayloadPtr((current_head + index) % capacity),
408 }
409 return ErrorCode::OK;
410 }
411
415 void Reset()
416 {
417 head_.store(0, std::memory_order_relaxed);
418 tail_.store(0, std::memory_order_relaxed);
419 }
420
425 size_t Size() const
426 {
427 const auto current_head = head_.load(std::memory_order_acquire);
428 const auto current_tail = tail_.load(std::memory_order_acquire);
429 return (current_tail >= current_head)
430 ? (current_tail - current_head)
431 : (RingCapacity() - current_head + current_tail);
432 }
433
438 size_t EmptySize() const { return capacity_ - Size(); }
439
444 size_t MaxSize() const { return capacity_; }
445
446 private:
453 std::byte* PayloadPtr(IndexType index) { return payloads_ + index * payload_stride_; }
454
462 const std::byte* PayloadPtr(IndexType index) const
463 {
464 return payloads_ + index * payload_stride_;
465 }
466
472 size_t RingCapacity() const { return capacity_ + 1; }
473
479 IndexType Increment(IndexType index) const { return (index + 1) % RingCapacity(); }
480
489
496 static size_t AlignUpChecked(size_t size, size_t align)
497 {
498 ASSERT(align > 0);
499 ASSERT((align & (align - 1)) == 0);
500 ASSERT(size <= std::numeric_limits<size_t>::max() - (align - 1));
501 return ((size + align - 1) / align) * align;
502 }
503
504 static size_t ComputeStride(size_t element_size, size_t element_align)
505 {
506 ASSERT(element_size > 0);
507 ASSERT(element_align > 0);
508 ASSERT((element_align & (element_align - 1)) == 0);
509 return AlignUpChecked(element_size, element_align);
510 }
511
518 static size_t MultiplyChecked(size_t lhs, size_t rhs)
519 {
520 if (lhs == 0 || rhs == 0)
521 {
522 return 0;
523 }
524
525 ASSERT(lhs <= std::numeric_limits<size_t>::max() / rhs);
526 return lhs * rhs;
527 }
528
529 const size_t element_size_;
530 const size_t capacity_;
531 const size_t payload_alloc_align_;
533 const size_t payload_stride_;
535 std::byte* payloads_;
536
537 alignas(LibXR::CONCURRENCY_ALIGNMENT) std::atomic<
539 alignas(LibXR::CONCURRENCY_ALIGNMENT) std::atomic<
541};
542} // namespace LibXR
static void FastCopy(void *dst, const void *src, size_t size)
快速内存拷贝 / Fast memory copy
单生产者单消费者字节队列内核 / Single-producer single-consumer byte-queue core
size_t MaxSize() const
获取队列最大容量 / Get the maximum queue capacity
void Reset()
重置队列状态 / Reset the queue state
SPSCQueueBase(SPSCQueueBase &&)
禁止移动构造。 Non-movable.
~SPSCQueueBase()
析构 SPSC 字节队列内核 / Destroy the SPSC byte-queue core
std::atomic< IndexType > tail_
下一个待入队的环形下标。 Next ring index to enqueue.
SPSCQueueBase(size_t element_size, size_t element_align, size_t capacity)
构造 SPSC 字节队列内核并指定元素对齐 / Construct the SPSC byte-queue core with explicit element alignment
IndexType Increment(IndexType index) const
沿环形缓冲区推进一个槽位 / Advance one slot along the ring
static size_t MultiplyChecked(size_t lhs, size_t rhs)
安全地计算两个字节数的乘积 / Safely multiply two byte counts
size_t IndexType
环形缓冲区索引类型 / Ring-buffer index type.
const size_t payload_alloc_align_
SPSCQueueBase(const SPSCQueueBase &)
禁止拷贝构造。 Non-copyable.
ErrorCode PeekBytes(void *value)
按字节查看一个队头 payload 但不出队 / Peek one front payload by bytes without dequeuing it
std::byte * payloads_
payload 字节缓冲区。 Byte buffer storing payloads.
ErrorCode PeekBatchBytes(void *data, size_t count)
按字节批量查看多个 payload 但不出队 / Peek multiple payloads by bytes without dequeuing them
size_t Size() const
获取当前已用元素数 / Get the current element count
static size_t AlignUpChecked(size_t size, size_t align)
安全地向上对齐字节数 / Safely align one byte count upward
const size_t capacity_
队列容量。 Queue capacity.
std::byte * PayloadPtr(IndexType index)
获取指定槽位 payload 起始地址 / Get the payload base address of one slot
SPSCQueueBase(size_t element_size, size_t capacity)
构造 SPSC 字节队列内核 / Construct the SPSC byte-queue core
std::atomic< IndexType > head_
下一个待出队的环形下标。 Next ring index to dequeue.
ErrorCode PushBytes(const void *value)
按字节入队一个 payload / Enqueue one payload by bytes
SPSCQueueBase & operator=(const SPSCQueueBase &)
禁止拷贝赋值。 Non-copy-assignable.
SPSCQueueBase & operator=(SPSCQueueBase &&)
禁止移动赋值。 Non-move-assignable.
size_t EmptySize() const
获取剩余空槽数 / Get the current free-slot count
size_t RingCapacity() const
获取环形缓冲区的物理槽位总数 / Get the physical ring-slot count
ErrorCode PopBatchBytes(void *data, size_t count)
按字节批量出队多个 payload / Dequeue multiple payloads by bytes
ErrorCode PushBatchBytes(const void *data, size_t count)
按字节批量入队多个 payload / Enqueue multiple payloads by bytes
ErrorCode PopBytesWithReader(size_t count, Reader &&reader)
通过读取器回调批量出队 payload / Dequeue payloads through a reader callback
const std::byte * PayloadPtr(IndexType index) const
获取指定槽位 payload 起始地址(只读) / Get the payload base address of one slot (const)
const size_t element_size_
单个 payload 的字节数。 Byte size of one payload.
ErrorCode PopBytes(void *value=nullptr)
按字节出队一个 payload;传空指针时仅丢弃队头元素 / Dequeue one payload by bytes; pass null to discard the front item only
ErrorCode PushBytesWithWriter(size_t count, Writer &&writer)
通过写入器回调批量入队 payload / Enqueue payloads through a writer callback
LibXR 命名空间
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举
@ PTR_NULL
空指针 | Null pointer
@ EMPTY
为空 | Empty
@ FULL
已满 | Full
@ OK
操作成功 | Operation successful
constexpr size_t CONCURRENCY_ALIGNMENT
并发结构对齐粒度(用于降低多核伪共享) / Alignment policy used by concurrency-oriented structures
Definition libxr_def.hpp:62