cmvr-es/cmvr-es/common/base/ring_buffer.h

446 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Created by xtkuang on 2025/5/30.
//
#ifndef CMVR_ES_RING_BUFFER_H
#define CMVR_ES_RING_BUFFER_H
#pragma once
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <deque>
#include <memory>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <utility>
#include <vector>
template<typename T>
class RingBuffer {
public:
explicit RingBuffer(size_t capacity) : capacity_(capacity) {
if (capacity_ == 0) {
throw std::invalid_argument("RingBuffer capacity must be greater than zero");
}
}
void push(const T& item) {
std::lock_guard<std::mutex> lock(mutex_);
if (buffer_.size() >= capacity_) {
buffer_.pop_front();
}
buffer_.push_back(item);
}
std::vector<T> getAll() const {
std::lock_guard<std::mutex> lock(mutex_);
return std::vector<T>(buffer_.begin(), buffer_.end());
}
void clear() {
std::lock_guard<std::mutex> lock(mutex_);
buffer_.clear();
}
size_t size() const {
std::lock_guard<std::mutex> lock(mutex_);
return buffer_.size();
}
private:
size_t capacity_;
std::deque<T> buffer_;
mutable std::mutex mutex_;
};
template<typename T>
class SPMCRingBuffer {
public:
explicit SPMCRingBuffer(size_t capacity)
: buffer_(capacity), capacity_(capacity) {
if (capacity_ == 0) {
throw std::invalid_argument("SPMCRingBuffer capacity must be greater than zero");
}
}
// 写入操作(仅支持单个生产者)
void push(const T& item) {
{
std::lock_guard<std::mutex> lock(mutex_);
buffer_[head_ % capacity_] = item;
++head_;
if (head_ - tail_ > capacity_) {
// 队列满,覆盖最旧的数据
tail_ = head_ - capacity_;
}
}
condition_.notify_all();
}
// 单消费者使用(内部 tail_
std::optional<T> pop() {
std::lock_guard<std::mutex> lock(mutex_);
if (tail_ >= head_) return std::nullopt;
T value = buffer_[tail_ % capacity_];
++tail_;
return value;
}
std::optional<T> getLast() const {
std::lock_guard<std::mutex> lock(mutex_);
if (tail_ >= head_) return std::nullopt;
return buffer_[(head_ - 1) % capacity_];
}
// 多消费者使用(每个读者独立维护 reader_tail。同一个 reader_tail 只能由
// 一个消费线程拥有,且不要把该游标与无参 pop() 的共享 tail_ 混合作为同一路读取。
std::optional<T> pop(size_t& reader_tail) const {
std::lock_guard<std::mutex> lock(mutex_);
if (reader_tail >= head_) return std::nullopt;
if (reader_tail < tail_) {
// 数据已被覆盖,跳过无效读取区间
reader_tail = tail_;
return std::nullopt;
}
T value = buffer_[reader_tail % capacity_];
reader_tail++;
return value;
}
// 在同一次加锁中把独立读游标跳到当前最新元素并读取,避免先 getHead()
// 再 pop() 时被高速覆盖造成的检查/读取竞态。
std::optional<T> getLatest(size_t& reader_tail) const {
std::lock_guard<std::mutex> lock(mutex_);
if (tail_ >= head_) {
return std::nullopt;
}
reader_tail = head_ - 1;
T value = buffer_[reader_tail % capacity_];
++reader_tail;
return value;
}
template<class Rep, class Period>
std::optional<T> waitPop(
size_t& reader_tail,
const std::chrono::duration<Rep, Period>& timeout) const {
std::unique_lock<std::mutex> lock(mutex_);
condition_.wait_for(lock, timeout, [&] { return reader_tail < head_; });
if (reader_tail >= head_) {
return std::nullopt;
}
if (reader_tail < tail_) {
reader_tail = tail_;
}
if (reader_tail >= head_) {
return std::nullopt;
}
T value = buffer_[reader_tail % capacity_];
++reader_tail;
return value;
}
size_t size() const {
std::lock_guard<std::mutex> lock(mutex_);
return head_ - tail_;
}
size_t getHead() const {
std::lock_guard<std::mutex> lock(mutex_);
return head_;
}
size_t getTail() const {
std::lock_guard<std::mutex> lock(mutex_);
return tail_;
}
bool empty() const {
return size() == 0;
}
bool full() const {
return size() >= capacity_;
}
void clear() {
{
std::lock_guard<std::mutex> lock(mutex_);
// Keep sequence numbers monotonic so cursors created before clear()
// cannot alias newly published slots after the reset.
tail_ = head_;
}
condition_.notify_all();
}
private:
mutable std::vector<T> buffer_;
const size_t capacity_;
mutable std::mutex mutex_;
mutable std::condition_variable condition_;
size_t head_{0}; // 单调写序号clear() 仅推进 tail_避免旧游标 ABA。
size_t tail_{0}; // 当前仍保留的最旧序号,同时也是 pop() 的共享读指针。
};
// 线程安全的多消费者广播缓冲区。缓冲区只保存不可变共享对象,消费者通过各自
// 的 Cursor 独立前进;慢消费者被覆盖的数据会累计到 Cursor::dropped_count。
// Cursor 是单线程所有权对象,不可由多个线程同时读写;每个消费者应创建自己的 Cursor。
template<typename T>
class BroadcastFrameRing {
public:
using ValuePtr = std::shared_ptr<const T>;
enum class StartPosition {
NEXT_PUBLISHED,
OLDEST_AVAILABLE,
LATEST_AVAILABLE
};
struct Cursor {
uint64_t generation{0};
uint64_t next_sequence{0};
uint64_t dropped_count{0};
StartPosition start_position{StartPosition::NEXT_PUBLISHED};
private:
bool generation_changed{false};
uint64_t reported_dropped_count{0};
friend class BroadcastFrameRing<T>;
};
struct ReadResult {
ValuePtr value;
uint64_t generation{0};
uint64_t sequence{0};
uint64_t dropped_count{0};
uint64_t dropped_since_last_read{0};
bool generation_changed{false};
};
struct Stats {
size_t capacity{0};
size_t size{0};
uint64_t generation{0};
uint64_t next_sequence{0};
uint64_t dropped_count{0};
bool closed{false};
};
explicit BroadcastFrameRing(const size_t capacity)
: capacity_(capacity) {
if (capacity_ == 0) {
throw std::invalid_argument("BroadcastFrameRing capacity must be greater than zero");
}
}
BroadcastFrameRing(const BroadcastFrameRing&) = delete;
BroadcastFrameRing& operator=(const BroadcastFrameRing&) = delete;
Cursor makeCursor(const StartPosition start_position = StartPosition::NEXT_PUBLISHED) const {
std::lock_guard<std::mutex> lock(mutex_);
Cursor cursor;
cursor.generation = generation_;
cursor.start_position = start_position;
cursor.next_sequence = startSequenceLocked_(start_position);
return cursor;
}
std::optional<uint64_t> publish(ValuePtr value) {
if (!value) {
return std::nullopt;
}
std::optional<uint64_t> published_sequence;
{
std::lock_guard<std::mutex> lock(mutex_);
if (closed_) {
return std::nullopt;
}
const uint64_t sequence = next_sequence_++;
if (entries_.size() == capacity_) {
entries_.pop_front();
++dropped_count_;
}
entries_.push_back(Entry{generation_, sequence, std::move(value)});
published_sequence = sequence;
}
condition_.notify_all();
return published_sequence;
}
std::optional<ReadResult> tryRead(Cursor& cursor) const {
std::lock_guard<std::mutex> lock(mutex_);
return tryReadLocked_(cursor);
}
// Low-latency consumers can use this before reading to abandon an excessive
// backlog atomically. When the number of currently readable entries exceeds
// maximum_pending_frames, every pending entry is discarded and the cursor is
// advanced to the next sequence that will be published. Frames already
// overwritten by the ring and frames actively discarded here are both
// reflected in Cursor::dropped_count; the next successful read reports their
// sum through ReadResult::dropped_since_last_read.
uint64_t discardPendingIfExceeds(
Cursor& cursor,
const size_t maximum_pending_frames) const {
std::lock_guard<std::mutex> lock(mutex_);
synchronizeCursorGenerationLocked_(cursor);
if (!entries_.empty()) {
const uint64_t oldest_sequence = entries_.front().sequence;
if (cursor.next_sequence < oldest_sequence) {
cursor.dropped_count += oldest_sequence - cursor.next_sequence;
cursor.next_sequence = oldest_sequence;
}
}
const uint64_t pending =
cursor.next_sequence < next_sequence_
? next_sequence_ - cursor.next_sequence
: 0;
if (pending <= static_cast<uint64_t>(maximum_pending_frames)) {
return 0;
}
cursor.next_sequence = next_sequence_;
cursor.dropped_count += pending;
return pending;
}
template<class Rep, class Period>
std::optional<ReadResult> waitRead(
Cursor& cursor,
const std::chrono::duration<Rep, Period>& timeout) const {
const auto deadline = std::chrono::steady_clock::now() + timeout;
std::unique_lock<std::mutex> lock(mutex_);
while (true) {
if (auto result = tryReadLocked_(cursor)) {
return result;
}
if (closed_) {
return std::nullopt;
}
if (condition_.wait_until(lock, deadline) == std::cv_status::timeout) {
return tryReadLocked_(cursor);
}
}
}
// 开始一个新的发布代次。旧 Cursor 会在下一次成功读取时收到
// generation_changed=true序号从 0 重新开始。
uint64_t reset() {
uint64_t generation = 0;
{
std::lock_guard<std::mutex> lock(mutex_);
entries_.clear();
++generation_;
next_sequence_ = 0;
closed_ = false;
generation = generation_;
}
condition_.notify_all();
return generation;
}
void close() {
{
std::lock_guard<std::mutex> lock(mutex_);
closed_ = true;
}
condition_.notify_all();
}
bool closed() const {
std::lock_guard<std::mutex> lock(mutex_);
return closed_;
}
Stats stats() const {
std::lock_guard<std::mutex> lock(mutex_);
return Stats{capacity_, entries_.size(), generation_, next_sequence_, dropped_count_, closed_};
}
private:
struct Entry {
uint64_t generation;
uint64_t sequence;
ValuePtr value;
};
uint64_t startSequenceLocked_(const StartPosition start_position) const {
if (entries_.empty()) {
return next_sequence_;
}
switch (start_position) {
case StartPosition::OLDEST_AVAILABLE:
return entries_.front().sequence;
case StartPosition::LATEST_AVAILABLE:
return entries_.back().sequence;
case StartPosition::NEXT_PUBLISHED:
default:
return next_sequence_;
}
}
void synchronizeCursorGenerationLocked_(Cursor& cursor) const {
if (cursor.generation == generation_) {
return;
}
cursor.generation = generation_;
cursor.next_sequence = startSequenceLocked_(cursor.start_position);
cursor.generation_changed = true;
}
std::optional<ReadResult> tryReadLocked_(Cursor& cursor) const {
synchronizeCursorGenerationLocked_(cursor);
if (entries_.empty()) {
return std::nullopt;
}
const uint64_t oldest_sequence = entries_.front().sequence;
if (cursor.next_sequence < oldest_sequence) {
cursor.dropped_count += oldest_sequence - cursor.next_sequence;
cursor.next_sequence = oldest_sequence;
}
if (cursor.next_sequence >= next_sequence_) {
return std::nullopt;
}
const size_t index = static_cast<size_t>(cursor.next_sequence - oldest_sequence);
if (index >= entries_.size()) {
return std::nullopt;
}
const Entry& entry = entries_[index];
++cursor.next_sequence;
const uint64_t dropped_since_last = cursor.dropped_count - cursor.reported_dropped_count;
cursor.reported_dropped_count = cursor.dropped_count;
ReadResult result;
result.value = entry.value;
result.generation = entry.generation;
result.sequence = entry.sequence;
result.dropped_count = cursor.dropped_count;
result.dropped_since_last_read = dropped_since_last;
result.generation_changed = cursor.generation_changed;
cursor.generation_changed = false;
return result;
}
const size_t capacity_;
mutable std::mutex mutex_;
mutable std::condition_variable condition_;
std::deque<Entry> entries_;
uint64_t generation_{1};
uint64_t next_sequence_{0};
uint64_t dropped_count_{0};
bool closed_{false};
};
#endif //CMVR_ES_RING_BUFFER_H