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 提前截断链表。

解决方案:

  1. 状态校验与自旋(Spin-wait): Consumer 处理完当前节点后,对比全局 Tail。若 Tail 已改变,但当前节点的 next == UNCONNECTED,说明有 Producer 正在链接。Consumer 会执行极短的 CPU 自旋等待(yield/pause),直到 next 被赋值。
  2. 后台线程兜底(KeepWrite): 若 Consumer 达到单次写入上限,或自旋超时必须返回业务逻辑,会将尚未发送的链表指针原子性地移交给后台专用的 KeepWrite 线程继续处理,确保数据不丢失且 Consumer 不被长期阻塞。

💡 性能本质总结

  1. 缩小临界区:将同步范围从 [加锁 -> I/O -> 解锁] 压缩至 [原子指令]
  2. 消除系统调用阻塞:99% 的线程只做内存级别的链表追加,耗时趋近于 0。
  3. 负反馈优化:并发度越高,MPSC 链表积压越长,单次 writev 聚集写的数据量越大,化高并发冲突为高吞吐量契机

参考:

1.没啃透无锁队列,高并发底层你只懂了皮毛!