disruptor 的核心优势在于预分配内存、环形索引和单生产者/单消费者模型下避免原子操作,而非单纯无锁;它要求固定容量、禁止动态扩容、推荐线程绑定,以实现百万级吞吐与低延迟。

为什么不能直接用 std::queue 或 ConcurrentQueue 就完事?
因为它们要么锁太重(std::queue + std::mutex),要么内存布局非连续、缓存不友好(如 moodycamel::ConcurrentQueue 的链式节点分配)。Disruptor 的核心不是“无锁”,而是“预分配 + 环形索引 + 单生产者/单消费者模型下绕过原子操作”。你如果真要高性能,就得接受约束:比如禁止动态扩容、必须提前知道最大容量、最好固定线程绑定。
- 常见错误现象:
std::atomic<int>::fetch_add(1)</int>在高争用下仍会引发总线锁,比环形缓冲区里用std::memory_order_relaxed+ 位置预判慢 3–5 倍 - 使用场景:日志采集、实时风控规则匹配、高频行情转发——数据吞吐 > 100 万条/秒,延迟要求
- 性能影响:环形缓冲区的
cache line对齐(alignas(64))比单纯无锁关键,否则伪共享会让两个相邻std::atomic<long></long>相互拖慢
RingBuffer 的索引怎么算才不越界又不依赖取模?
取模(% capacity)在 x86 上虽快,但分支预测失败时有代价;更关键是它隐藏了“capacity 必须是 2 的幂”这个硬约束。Disruptor 用位运算替代:index & (capacity - 1),前提是 capacity 是 2 的幂——这样编译器能优化成 and 指令,且无分支。
- 容易踩的坑:初始化
capacity = 1000,结果capacity - 1 = 999不是全 1 二进制,位运算失效 → 数据写到错误槽位 - 实操建议:构造时强制校验
if ((capacity & (capacity - 1)) != 0) throw std::invalid_argument("capacity must be power of 2"); - 参数差异:
RingBuffer不存实际数据,只存long类型的序列号(cursor/gatingSequence),数据另放堆/对象池,避免拷贝和 GC 压力
如何让多个消费者不互相阻塞,又保证顺序可见?
Disruptor 不靠锁,靠“游标分离”:每个消费者维护自己的 sequence,生产者只管推进 cursor,而所有消费者共同等待的屏障是 min(gatingSequences...)。这本质是“读写分离 + 批量确认”。
- 常见错误现象:消费者 A 处理慢,B 却卡住——因为 B 的
waitFor()等的是全局最小值,A 没提交就拦住所有人 - 实操建议:对非严格顺序场景,用
WorkProcessor模式,把一个RingBuffer逻辑分片给多个工作线程,每线程独占一段sequence,避免竞争 - 兼容性影响:Java 版 Disruptor 依赖
sun.misc.Unsafe实现无锁,C++ 版必须用std::atomic_thread_fence显式控制内存序,memory_order_acquire和memory_order_release不能省
为什么 publish 要分 tryNext → get → publish 三步?
这不是为了炫技,而是把“申请位置”和“提交完成”解耦。中间那步 get 允许你在拿到槽位后做耗时操作(比如反序列化、校验),只要不调 publish,其他消费者就看不到这条数据——相当于软件实现的“事务边界”。
- 容易踩的坑:跳过
tryNext直接get(cursor),结果cursor已被覆盖,读到脏数据 - 使用场景:金融订单撮合中,需先查持仓再决定是否允许下单,这段逻辑必须在
publish前完成,否则状态不一致 - 性能影响:
tryNext内部是compare_exchange_weak循环,若争用高,应考虑批量申请(next(n))减少 CAS 次数
真正难的从来不是“怎么写无锁”,而是怎么设计业务逻辑去适配环形缓冲区的节奏感——比如消费者不能阻塞,那就得把重 IO 拆出去;比如序列号不能回退,那就得接受偶尔丢弃旧事件。这些约束不是缺陷,是交换来的确定性。











