无锁写入的三步与 alignas 布局
counter 是绝对偏移(一直累加的 uint64_t),取 % CAP 才映射到 buffer 的物理槽位。绝对偏移免去了「指针绕回之后无法判别先后」的麻烦:两个 counter 的差值就是在途写入的长度。producer 不设背压、从不等待 consumer,写满一圈即覆盖最旧的数据。
1 · 一次写入的三步
一次 write() 的三步顺序是固定的:先推进 mWriteCounter 把即将写的区间标为写入中,再把数据拷进去,最后推进 mReadCounter 发布。
注 · 三步的顺序决定了 readers 看到什么。推进 mWriteCounter 等于抢先声明 [r, r+n) 这段作废,此后开始读的 consumer 会看到它属于写入区而跳过;数据拷完再推进 mReadCounter,这一步才是「发布」。承担可见性的也只有第二个 store:它的
release 必须与 consumer 侧对 mReadCounter 的 acquire 载入配对,才能保证拷贝的字节先于新 counter 可见。第一个 store 需要的是 store-store 次序,release 给不了这层保证——x86-64 的 TSO 模型下普通 store 之间本就不重排,可移植的写法要在其后插一道
atomic_thread_fence。
警示 ·「跳过写入区」拦不住所有撕裂。反例:consumer 在 producer 推进 W 之前已判定某段可读并开始拷出,producer 随即推进 W 覆写同一段——前置检查一律拦不住这种「读时合法、拷贝中被追尾」。这类不等待的 SPMC 队列靠事后校验:consumer 拷完再取一次 counter,发现自己被套圈就丢弃重来。代价是慢 consumer 会丢消息,本设计没有投递保证。
注 · 页面代码里的 % CAP 是为可读性写的。真实实现会把容量取 2 的幂,用 & (CAP - 1) 代替取模——非 2 的幂时 % 是硬件除法,几十个周期,在纳秒级路径上首先要被干掉。同理,跨界的那次拷贝要拆成两段 memcpy,单次连续拷贝会越过
mBuffer 末尾。
2 · alignas 与 false sharing
CPU 缓存以 cache line(通常 64 字节)为单位。若两个 counter 落在同一条 cache line,producer 改 mWriteCounter 会让整条 line 失效——即便 consumer 只想读 mReadCounter,也被迫从内存 / 远核重新加载。这就是
false sharing(伪共享)。与教科书里「两个线程各写各的变量」不同,本设计是单 producer 写两个 counter、consumer 只读,失效同样发生。
注 · alignas 用空间换时间:每个 counter 多占大半条 cache line 的 padding,换来读写互不打扰的稳定低延迟。它只保证起始地址对齐,尾部并不天然独占——本例三个成员各自 alignas(64),后一个成员从新的一条 line 起头,前一个的剩余字节才成为无人共享的 padding。mBuffer
同样对齐,数据拷贝不会反过来作废 counter。
3 · 参考文献
- Gross, D. (2024). When Nanoseconds Matter: Ultrafast Trading Systems in C++. CppCon 2024. 本页是对其中 FastQueue 一节设计的可视化复现。https://www.youtube.com/watch?v=sX2nF1fW7kI
- std::memory_order. cppreference.
release/acquire/relaxed的语义定义。https://en.cppreference.com/cpp/atomic/memory_order - False sharing. Wikipedia. 成因(cache line 粒度与 MESI 一致性协议)与 padding / alignment 缓解手段。https://en.wikipedia.org/wiki/False_sharing
相关链接
- When Nanoseconds Matter: Ultrafast Trading Systems in C++ CppCon (David Gross) 本页设计的出处:FastQueue 的 counter / cache-line 布局、以及一整套纳秒级低延迟工程实践。
- False sharing Wikipedia 伪共享的成因(cache line 粒度 + MESI 一致性协议)与典型的 padding / alignment 缓解手段。
- std::memory_order cppreference 本页用到的 release / acquire / relaxed 各自的语义定义。
- alignas specifier cppreference 对齐说明符的用法;配合 std::hardware_destructive_interference_size 取 cache line 大小。
- Circular buffer Wikipedia 环形缓冲区基础:用单调 counter + 取模映射物理槽位,以及有背压时区分空 / 满的几种约定。