双缓冲落盘核心是业务线程零等待:需用三状态机(READY_TO_WRITE→SWITCHING→READY_TO_FLUSH)配合std::atomic和compare_exchange_weak控制切换,写入必须对齐log_entry结构、避免字节撕裂,并辅以memory_order_release/acquire和内存屏障防崩溃丢日志。

双缓冲落盘不是加两个 std::array 就完事,核心是让业务线程永远不等磁盘——写入必须原子、切换必须无锁、刷盘必须批量,否则吞吐量卡在 10 万条/秒都难。
为什么 std::atomic 直接交换缓冲区指针容易丢日志
常见错误是只用 std::atomic<char></char> 存当前可写地址,业务线程调 append() 时直接往里 memcpy,但没做「写入完成」同步。一旦后台线程在你 memcpy 到一半时原子交换指针,它就会读到未对齐、截断甚至字节撕裂的日志项。
- 必须配合「已用长度」的原子变量(如
std::atomic<size_t></size_t>)和内存序(memory_order_release写入后、memory_order_acquire切换前) - 更稳妥的做法是用三状态机:
READY_TO_WRITE → SWITCHING → READY_TO_FLUSH,靠std::atomic<int></int>+compare_exchange_weak控制跃迁 - 切换前,业务线程要确保当前 buffer 已完整填满一条日志(按
struct log_entry对齐),不能停在中间字段上
buffer 大小设成 64KB 还是 4MB?看你的延迟容忍和硬件
这不是越大越好,也不是越小越快,得匹配 write() 的系统调用开销和磁盘实际响应。
- 64KB(即
65536)适合 SSD + 高频小日志:单次write()延迟稳定在 10–50μs,切换频繁但 CPU 开销低 - 4MB 适合机械盘或日志密度高(如每秒百万条):减少
write()次数,但单次刷盘可能卡住 5ms+,需搭配posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED)清 page cache - 千万别用
std::vector<char></char>或std::string当 buffer 底层——扩容触发realloc()会破坏原子性,且异常安全难保障
flush 线程该用 io_uring 还是阻塞 write()?别被文档带偏
io_uring 在 kernel 5.11+ 确实快,但真用起来坑多;而阻塞 write() 配合正确参数,实测吞吐反超。
立即学习“C++免费学习笔记(深入)”;
- 用
io_uring必须满足:文件以O_DIRECT | O_SYNC打开、buffer 地址用posix_memalign(4096, size)对齐、每个io_uring_sqe提交前重置user_data和flags - 普通场景推荐「专用 flush 线程 +
write()+fsync()分离」:每满 4KB 或空闲超 1s 触发fsync(),避免每次write()都等磁盘确认 -
O_SYNC和O_DSYNC必须禁用——它们会让每次write()变成同步落盘,双缓冲彻底失效
崩溃后最后 500ms 日志怎么保?fsync 不够,得靠元数据 + 内存屏障
仅靠 fsync() 或 io_uring 的 IORING_SETUP_IOPOLL 无法防崩溃丢日志,因为内核 page cache 和磁盘 write cache 仍可能滞留数据。
- 必须在每次关键切换(如 buffer 从
SWITCHING置为READY_TO_FLUSH)后插入std::atomic_thread_fence(std::memory_order_seq_cst) - 配合元数据文件记录「最后成功刷盘的 buffer ID + offset」,重启时比对并补全未落盘部分
- 如果业务要求极强一致性,考虑用
open(..., O_DIRECT | O_SYNC)+write()+__builtin_ia32_sfence()(x86),但这会牺牲 30%+ 吞吐


















