mpsc
📌 bRPC 无锁 MPSC 写入机制技术笔记
🎯 核心设计思想:极致缩小同步区间
传统方案的性能瓶颈在于“临界区过大”:互斥锁的临界区包含了耗时的系统调用(Socket I/O)。 bRPC 机制的核心在于将网络 I/O 剥离出同步区间,将线程间的竞争极度压缩为一条 CPU 原子指令(指针交换)。
⚙️ 写入提交流程(无锁链表构建)
1. 节点初始化 新请求构建为 Node,next
指针置为特殊状态宏 UNCONNECTED,避免与常规的
NULL 混淆。
2. 极小临界区:Atomic Exchange 线程通过
std::atomic_exchange 竞争全局 Tail 指针,将
Tail 指向自身,并获取 Tail 的旧值。
这是整个写入流程中唯一的同步点,耗时仅需几个纳秒的 CPU
时钟周期。
3. 角色分化与无阻塞提交 根据
atomic_exchange 的返回值分化线程职责:
- Producer(旧值 != NULL):说明链表已有头部。执行
old_tail->next = current_node完成指针链接。此操作纯内存赋值,完成后线程立刻返回,完全避开 I/O 阻塞。 - Consumer(旧值 == NULL):说明当前节点是链表首节点。该线程承担 Consumer 职责,负责后续的 I/O 写入。
4. 批量 I/O(Batching) Consumer 顺着链表收集多个
Producer 提交的 Node,在无锁状态下调用一次
writev 集中进行系统调用,大幅降低上下文切换开销。
🛡️ 竞态条件(Race Condition)处理
隐患:时间差导致断链 Producer 执行完
atomic_exchange 后,尚未执行
old_tail->next = current_node 发生线程调度,导致
Consumer 读到 UNCONNECTED 提前截断链表。
解决方案:
- 状态校验与自旋(Spin-wait): Consumer
处理完当前节点后,对比全局
Tail。若Tail已改变,但当前节点的next == UNCONNECTED,说明有 Producer 正在链接。Consumer 会执行极短的 CPU 自旋等待(yield/pause),直到next被赋值。 - 后台线程兜底(KeepWrite): 若 Consumer
达到单次写入上限,或自旋超时必须返回业务逻辑,会将尚未发送的链表指针原子性地移交给后台专用的
KeepWrite线程继续处理,确保数据不丢失且 Consumer 不被长期阻塞。
💡 性能本质总结
- 缩小临界区:将同步范围从
[加锁 -> I/O -> 解锁]压缩至[原子指令]。 - 消除系统调用阻塞:99% 的线程只做内存级别的链表追加,耗时趋近于 0。
- 负反馈优化:并发度越高,MPSC 链表积压越长,单次
writev聚集写的数据量越大,化高并发冲突为高吞吐量契机。
参考: