libxr
Embedded C++ framework for bare-metal, RTOS and Linux
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 // payload_alloc_align_ 是由 alignof(std::max_align_t) 初始化的 const 成员。
72 // payload_alloc_align_ is a const member initialised from alignof(std::max_align_t).
73 static_assert(alignof(std::max_align_t) > 0 &&
74 (alignof(std::max_align_t) & (alignof(std::max_align_t) - 1)) == 0);
75 ASSERT(element_size_ > 0);
76 ASSERT(capacity_ > 0);
77 ASSERT(capacity_ <= std::numeric_limits<size_t>::max() - 1);
78
79 const size_t payload_bytes = MultiplyChecked(payload_stride_, RingCapacity());
80 payloads_ = static_cast<std::byte*>(
81 ::operator new[](payload_bytes, std::align_val_t(payload_alloc_align_)));
82 }
83
84 public:
89 {
90 ::operator delete[](payloads_, std::align_val_t(payload_alloc_align_));
91 }
92
101 ErrorCode PushBytes(const void* value)
102 {
103 if (value == nullptr)
104 {
105 return ErrorCode::PTR_NULL;
106 }
107
108 const auto current_tail = tail_.load(std::memory_order_relaxed);
109 const auto next_tail = Increment(current_tail);
110
111 if (next_tail == head_.load(std::memory_order_acquire))
112 {
113 return ErrorCode::FULL;
114 }
115
116 LibXR::Memory::FastCopy(PayloadPtr(current_tail), value, element_size_);
117 tail_.store(next_tail, std::memory_order_release);
118 return ErrorCode::OK;
119 }
120
130 ErrorCode PopBytes(void* value = nullptr)
131 {
132 const auto current_head = head_.load(std::memory_order_relaxed);
133
134 if (current_head == tail_.load(std::memory_order_acquire))
135 {
136 return ErrorCode::EMPTY;
137 }
138
139 if (value != nullptr)
140 {
141 LibXR::Memory::FastCopy(value, PayloadPtr(current_head), element_size_);
142 }
143
144 head_.store(Increment(current_head), std::memory_order_release);
145 return ErrorCode::OK;
146 }
147
157 ErrorCode PeekBytes(void* value)
158 {
159 if (value == nullptr)
160 {
161 return ErrorCode::PTR_NULL;
162 }
163
164 const auto current_head = head_.load(std::memory_order_relaxed);
165 if (current_head == tail_.load(std::memory_order_acquire))
166 {
167 return ErrorCode::EMPTY;
168 }
169
170 LibXR::Memory::FastCopy(value, PayloadPtr(current_head), element_size_);
171 return ErrorCode::OK;
172 }
173
184 ErrorCode PushBatchBytes(const void* data, size_t count)
185 {
186 if (count == 0U)
187 {
188 return ErrorCode::OK;
189 }
190 if (data == nullptr)
191 {
192 return ErrorCode::PTR_NULL;
193 }
194
195 const auto current_tail = tail_.load(std::memory_order_relaxed);
196 const auto current_head = head_.load(std::memory_order_acquire);
197 const size_t capacity = RingCapacity();
198 const size_t free_space = (current_tail >= current_head)
199 ? (capacity - (current_tail - current_head) - 1)
200 : (current_head - current_tail - 1);
201
202 if (free_space < count)
203 {
204 return ErrorCode::FULL;
205 }
206
207 const auto* src = static_cast<const std::byte*>(data);
208 for (size_t index = 0; index < count; ++index)
209 {
210 LibXR::Memory::FastCopy(PayloadPtr((current_tail + index) % capacity),
211 src + index * element_size_, element_size_);
212 }
213
214 tail_.store((current_tail + count) % capacity, std::memory_order_release);
215 return ErrorCode::OK;
216 }
217
233 template <typename Writer>
234 ErrorCode PushBytesWithWriter(size_t count, Writer&& writer)
235 {
236 if (count == 0U)
237 {
238 return ErrorCode::OK;
239 }
240
241 const auto current_tail = tail_.load(std::memory_order_relaxed);
242 const auto current_head = head_.load(std::memory_order_acquire);
243 const size_t capacity = RingCapacity();
244 const size_t free_space = (current_tail >= current_head)
245 ? (capacity - (current_tail - current_head) - 1)
246 : (current_head - current_tail - 1);
247
248 if (free_space < count)
249 {
250 return ErrorCode::FULL;
251 }
252
253 const size_t first_chunk = std::min(count, capacity - current_tail);
254 Writer& writer_ref = writer;
255 const ErrorCode first_ec = writer_ref(PayloadPtr(current_tail), first_chunk);
256 if (first_ec != ErrorCode::OK)
257 {
258 return first_ec;
259 }
260
261 if (count > first_chunk)
262 {
263 const ErrorCode second_ec = writer_ref(PayloadPtr(0), count - first_chunk);
264 if (second_ec != ErrorCode::OK)
265 {
266 return second_ec;
267 }
268 }
269
270 tail_.store((current_tail + count) % capacity, std::memory_order_release);
271 return ErrorCode::OK;
272 }
273
289 template <typename Writer>
290 size_t ProduceWithWriter(size_t limit, Writer&& writer)
291 {
292 if (limit == 0U)
293 {
294 return 0U;
295 }
296
297 const auto current_tail = tail_.load(std::memory_order_relaxed);
298 const auto current_head = head_.load(std::memory_order_acquire);
299 const size_t capacity = RingCapacity();
300 const size_t free_space = (current_tail >= current_head)
301 ? (capacity - (current_tail - current_head) - 1U)
302 : (current_head - current_tail - 1U);
303 const size_t offered = std::min(limit, free_space);
304 if (offered == 0U)
305 {
306 return 0U;
307 }
308
309 const size_t first_count = std::min(offered, capacity - current_tail);
310 const size_t second_count = offered - first_count;
311 void* const second = second_count == 0U ? nullptr : PayloadPtr(0U);
312 const size_t produced =
313 writer(PayloadPtr(current_tail), first_count, second, second_count);
314 ASSERT(produced <= offered);
315 if (produced != 0U)
316 {
317 tail_.store((current_tail + produced) % capacity, std::memory_order_release);
318 }
319 return produced;
320 }
321
331 ErrorCode PopBatchBytes(void* data, size_t count)
332 {
333 if (count == 0U)
334 {
335 return ErrorCode::OK;
336 }
337
338 const auto current_head = head_.load(std::memory_order_relaxed);
339 const auto current_tail = tail_.load(std::memory_order_acquire);
340 const size_t capacity = RingCapacity();
341 const size_t available = (current_tail >= current_head)
342 ? (current_tail - current_head)
343 : (capacity - current_head + current_tail);
344
345 if (available < count)
346 {
347 return ErrorCode::EMPTY;
348 }
349
350 auto* dst = static_cast<std::byte*>(data);
351 if (dst != nullptr)
352 {
353 for (size_t index = 0; index < count; ++index)
354 {
355 LibXR::Memory::FastCopy(dst + index * element_size_,
356 PayloadPtr((current_head + index) % capacity),
357 element_size_);
358 }
359 }
360
361 head_.store((current_head + count) % capacity, std::memory_order_release);
362 return ErrorCode::OK;
363 }
364
381 template <typename Reader>
382 ErrorCode PopBytesWithReader(size_t count, Reader&& reader)
383 {
384 if (count == 0U)
385 {
386 return ErrorCode::OK;
387 }
388
389 const auto current_head = head_.load(std::memory_order_relaxed);
390 const auto current_tail = tail_.load(std::memory_order_acquire);
391 const size_t capacity = RingCapacity();
392 const size_t available = (current_tail >= current_head)
393 ? (current_tail - current_head)
394 : (capacity - current_head + current_tail);
395
396 if (available < count)
397 {
398 return ErrorCode::EMPTY;
399 }
400
401 const size_t first_chunk = std::min(count, capacity - current_head);
402 Reader& reader_ref = reader;
403 const ErrorCode first_ec = reader_ref(PayloadPtr(current_head), first_chunk);
404 if (first_ec != ErrorCode::OK)
405 {
406 return first_ec;
407 }
408
409 if (count > first_chunk)
410 {
411 const ErrorCode second_ec = reader_ref(PayloadPtr(0), count - first_chunk);
412 if (second_ec != ErrorCode::OK)
413 {
414 return second_ec;
415 }
416 }
417
418 head_.store((current_head + count) % capacity, std::memory_order_release);
419 return ErrorCode::OK;
420 }
421
437 template <typename Reader>
438 size_t ConsumeWithReader(size_t limit, Reader&& reader)
439 {
440 if (limit == 0U)
441 {
442 return 0U;
443 }
444
445 const auto current_head = head_.load(std::memory_order_relaxed);
446 const auto current_tail = tail_.load(std::memory_order_acquire);
447 const size_t capacity = RingCapacity();
448 const size_t available = (current_tail >= current_head)
449 ? (current_tail - current_head)
450 : (capacity - current_head + current_tail);
451 const size_t offered = std::min(limit, available);
452 if (offered == 0U)
453 {
454 return 0U;
455 }
456
457 const size_t first_count = std::min(offered, capacity - current_head);
458 const size_t second_count = offered - first_count;
459 const void* const second = second_count == 0U ? nullptr : PayloadPtr(0U);
460 const size_t accepted =
461 reader(PayloadPtr(current_head), first_count, second, second_count);
462 ASSERT(accepted <= offered);
463 if (accepted != 0U)
464 {
465 head_.store((current_head + accepted) % capacity, std::memory_order_release);
466 }
467 return accepted;
468 }
469
481 ErrorCode PeekBatchBytes(void* data, size_t count)
482 {
483 if (count == 0U)
484 {
485 return ErrorCode::OK;
486 }
487 if (data == nullptr)
488 {
489 return ErrorCode::PTR_NULL;
490 }
491
492 const auto current_head = head_.load(std::memory_order_relaxed);
493 const auto current_tail = tail_.load(std::memory_order_acquire);
494 const size_t capacity = RingCapacity();
495 const size_t available = (current_tail >= current_head)
496 ? (current_tail - current_head)
497 : (capacity - current_head + current_tail);
498
499 if (available < count)
500 {
501 return ErrorCode::EMPTY;
502 }
503
504 auto* dst = static_cast<std::byte*>(data);
505 for (size_t index = 0; index < count; ++index)
506 {
507 LibXR::Memory::FastCopy(dst + index * element_size_,
508 PayloadPtr((current_head + index) % capacity),
509 element_size_);
510 }
511 return ErrorCode::OK;
512 }
513
526 void Reset()
527 {
528 const auto current_tail = tail_.load(std::memory_order_acquire);
529 head_.store(current_tail, std::memory_order_release);
530 }
531
536 size_t Size() const
537 {
538 const auto current_head = head_.load(std::memory_order_acquire);
539 const auto current_tail = tail_.load(std::memory_order_acquire);
540 return (current_tail >= current_head)
541 ? (current_tail - current_head)
542 : (RingCapacity() - current_head + current_tail);
543 }
544
549 size_t EmptySize() const { return capacity_ - Size(); }
550
555 size_t MaxSize() const { return capacity_; }
556
557 private:
564 std::byte* PayloadPtr(IndexType index) { return payloads_ + index * payload_stride_; }
565
573 const std::byte* PayloadPtr(IndexType index) const
574 {
575 return payloads_ + index * payload_stride_;
576 }
577
583 size_t RingCapacity() const { return capacity_ + 1; }
584
590 IndexType Increment(IndexType index) const { return (index + 1) % RingCapacity(); }
591
595 SPSCQueueBase& operator=(const SPSCQueueBase&);
599 SPSCQueueBase& operator=(SPSCQueueBase&&);
600
607 static size_t AlignUpChecked(size_t size, size_t align)
608 {
609 DEV_ASSERT(align > 0);
610 DEV_ASSERT((align & (align - 1)) == 0);
611 ASSERT(size <= std::numeric_limits<size_t>::max() - (align - 1));
612 return ((size + align - 1) / align) * align;
613 }
614
615 static size_t ComputeStride(size_t element_size, size_t element_align)
616 {
617 ASSERT(element_size > 0);
618 ASSERT(element_align > 0);
619 ASSERT((element_align & (element_align - 1)) == 0);
620 return AlignUpChecked(element_size, element_align);
621 }
622
629 static size_t MultiplyChecked(size_t lhs, size_t rhs)
630 {
631 if (lhs == 0 || rhs == 0)
632 {
633 return 0;
634 }
635
636 ASSERT(lhs <= std::numeric_limits<size_t>::max() / rhs);
637 return lhs * rhs;
638 }
639
640 const size_t element_size_;
641 const size_t capacity_;
642 const size_t payload_alloc_align_;
644 const size_t payload_stride_;
646 std::byte* payloads_;
647
648 alignas(LibXR::CONCURRENCY_ALIGNMENT) std::atomic<
649 IndexType> head_;
650 alignas(LibXR::CONCURRENCY_ALIGNMENT) std::atomic<
651 IndexType> tail_;
652};
653} // 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()
从 consumer 侧丢弃当前可见的全部 payload / Discard all currently available payloads from the consumer side
~SPSCQueueBase()
析构 SPSC 字节队列内核 / Destroy the SPSC byte-queue core
SPSCQueueBase(size_t element_size, size_t element_align, size_t capacity)
构造 SPSC 字节队列内核并指定元素对齐 / Construct the SPSC byte-queue core with explicit element alignment
size_t IndexType
环形缓冲区索引类型 / Ring-buffer index type.
ErrorCode PeekBytes(void *value)
按字节查看一个队头 payload 但不出队 / Peek one front payload by bytes without dequeuing it
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
SPSCQueueBase(size_t element_size, size_t capacity)
构造 SPSC 字节队列内核 / Construct the SPSC byte-queue core
ErrorCode PushBytes(const void *value)
按字节入队一个 payload / Enqueue one payload by bytes
size_t ProduceWithWriter(size_t limit, Writer &&writer)
通过一次双 span 回调生产 payload 前缀 / Produce a payload prefix through one two-span writer callback.
size_t EmptySize() const
获取剩余空槽数 / Get the current free-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
ErrorCode PopBytes(void *value=nullptr)
按字节出队一个 payload;传空指针时仅丢弃队头元素 / Dequeue one payload by bytes; pass null to discard the front item only
size_t ConsumeWithReader(size_t limit, Reader &&reader)
通过一次双 span 回调消费 payload 前缀 / Consume a payload prefix through one two-span reader callback.
ErrorCode PushBytesWithWriter(size_t count, Writer &&writer)
通过写入器回调批量入队 payload / Enqueue payloads through a writer callback
LibXR 命名空间 / LibXR namespace.
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举 / Defines an enumeration for error codes
@ 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:61