MergeCell 合并机制:双平台事件流去重

MergeCell 合并机制:双平台事件流去重

项目链接:github.com/DeguiLiu/co...

同一目标、同一信号的事件可能以极高频率到达(传感器 1 kHz 上报,消费方只要 100 Hz 的最新值)。逐个传递浪费------消费方只关心最新值。需要一个合并单元:把同一 (target, signal) 的后续事件合并进单个槽位,只保留最新状态。

naive 的方案是锁保护一个槽位,但 ISR 上下文不能持有锁。MergeCell 用单个 std::atomic 状态 + CAS 状态机实现无锁合并。

四态状态机

stateDiagram-v2 [*] --> Empty Empty --> Published: try_publish() 置入新事件 Published --> Merging: try_acquire_merge() producer 认领覆写 Merging --> Published: release_merge() 覆写完成 Published --> Consuming: take_owning() dispatcher 取走所有权 Consuming --> Empty: release_empty() event_gc 后归位 note right of Empty: 槽位空闲 note right of Published: 持有已 post 事件<br/>可合并/可取走 note right of Merging: 生产者覆写 payload<br/>独占,其余 CAS 失败 note right of Consuming: dispatcher 消费中<br/>只允许归位

四个状态对应四种并发角色,全部通过 compare_exchange_strong 转移:

状态 持有者 允许的下一个动作
Empty try_publish 置入新事件
Published 已 post 的 Event* try_acquire_merge(覆写)/ take_owning(取走)
Merging 认领 merge 的 producer release_merge 回到 Published
Consuming 取走的 dispatcher release_empty 回到 Empty

CAS 失败永不阻塞

与 Treiber 栈不同,MergeCell 的 CAS 失败不重试、不旋转。每个转移方法失败就返回 false,调用方走降级路径:

flowchart TD A[producer 提交新事件] --> B{try_acquire_merge?} B -- 成功 --> C[覆写 payload] --> D[release_merge] B -- 失败 --> E{take_owning?} E -- 成功 --> F[取走旧事件消费] E -- 失败 --> G[fallback: 正常 staging 入队]
cpp 复制代码
bool try_acquire_merge(Event*& queued) noexcept
{
    MergeCellState expected = MergeCellState::Published;
    if (!state_.compare_exchange_strong(expected, MergeCellState::Merging,
                                        std::memory_order_acq_rel)) {
        return false;   // 不旋转,直接失败
    }
    queued = event_;
    return true;
}

合并是 best-effort 的,错过一次合并只是多入队一个事件。这解释了为什么 merge_wins > 0 的断言是错的------两个 producer 可以都 publish 成功、都来不及 merge,merge_wins == 0 完全合法。

内存序:acq_rel 边界 + relaxed 数据

MergeCell 的经典陷阱是状态原子与数据指针分离 。状态在 state_ 原子上,payload 在普通 Event* 上------必须用内存屏障把"状态转移"和"数据可见性"绑定:

cpp 复制代码
// producer 置入:先 CAS 拿到 Published,再写数据
MergeCellState expected = MergeCellState::Empty;
if (!state_.compare_exchange_strong(expected, MergeCellState::Published,
                                    std::memory_order_acq_rel)) {
    return false;
}
event_ = e;   // relaxed:acq_rel 边界已排序这次发布
sequenceDiagram participant P as Producer participant S as state_ (atomic) participant E as event_ (plain ptr) P->>S: CAS Empty→Published (acq_rel) Note over S: acq_rel MB 建立 P->>E: 写 event_ = e (relaxed) Note over P,E: 数据写在边界之后<br/>acquire 方可见 P->>S: try_acquire_merge (acq_rel) Note over S: consume 方 acquire 读取<br/>能读到已发布的 event_

try_publish 的 CAS 是 acq_rel------release 侧保证 event_ 写在状态翻转之前可见,后续任何通过 Published 状态的 acquire(如 try_acquire_merge 的 CAS)都能正确读到 event_MergeCell::state()memory_order_acquire 读,保证读状态时能看到对应 epoch 的数据。

引用计数配合

合并槽位持有的是已 post 的引用计数事件event_ref_inc 过)。dispatcher 在 Consuming 状态下 take_owning 拿到句柄,消费完必须 event_gc() 恰好一次,再 release_empty 归位。合并覆写时旧事件被新事件替代、旧事件回池;取走消费时槽位归空、事件最终回池。无泄漏、无双重释放。


提交记录:d6cc476(MergeCell 成员初始化 + acq_rel 定型)

相关推荐
蜗牛互联网5 分钟前
Claude放宽生命科学限制:代价是验证、分级和30天留存
java·人工智能·后端
上进小菜猪9 分钟前
被 32 位整数卡了 30 年脖子:PostgreSQL 事务号困局与 V9 的 64 位解法
后端
知识的搬运工旺仔2 小时前
CREATE INDEX CONCURRENTLY:线上建索引不阻塞 DML 的代价与坑
数据库·后端·sql
小蒜学长2 小时前
大学生健康饮食的智慧管理系统(代码+数据库+LW)
java·后端·springboot·大学生·健康饮食
小蒜学长2 小时前
基于Java的论坛数据可视化分析系统的设计与实现(代码+数据库+LW)
java·spring boot·后端·数据可视化·论坛系统
青山木2 小时前
RocketMQ 入门到原理(一):整体架构与消息的生命周期
java·分布式·后端·中间件·架构·rocketmq
黑马程序员毕设3 小时前
基于微信小程序的二手交易平台设计与实现报告
前端·人工智能·spring boot·后端·考研
十二同学啊3 小时前
Drools规则引擎:让复杂业务规则从代码中解耦
java·后端·开源
打工仔折腾 AI4 小时前
Pascal Editor 本地部署实战:Bun 启动 WebGPU 3D 编辑器并解决公网访问报错
人工智能·后端·python·性能优化