双缓冲区必须用固定大小内存块(如std::array)和原子指针切换,禁用std::vector或new[]动态分配,避免realloc破坏原子性;业务线程仅append()无锁写入,后台线程flush()时原子交换并只读访问,满则阻塞等待而非自旋,buffer大小取4kb~64kb兼顾缓存与i/o效率。

双缓冲区必须用固定大小内存块 + 原子指针切换,不能靠 std::vector 或裸 new[] 动态分配,否则写一半 realloc 会破坏原子性,日志就丢了。
怎么让业务线程不卡、后台线程不丢数据
核心是两块独立缓冲区(比如 buf_a 和 buf_b)永远处于“一写一刷”状态:
- 业务线程只调用
append(),内部仅做指针偏移 +memcpy,全程无系统调用、无锁、不等待 - 后台线程在
flush()中先原子交换出待刷 buffer(比如用std::atomic<char></char>),再只读访问它;交换后立刻把另一块设为可写 - buffer 满时,业务线程不能自旋空转耗 CPU,应阻塞式等待(如
cond_.wait_for(...))或按策略丢弃/告警 - buffer 大小建议取 4KB~64KB:太小导致频繁切换和上下文抖动;太大则单次
write()阻塞风险上升,且不利于 L1/L2 缓存命中
为什么别用 std::ofstream 做落盘
std::ofstream::write() 和 flush() 是同步阻塞的,哪怕开了缓冲,一旦磁盘延迟突增(比如 ext4 journal 卡顿、SSD GC),线程就被挂起——这直接废掉“异步”前提。
- 真正可控的是 C stdio:用
FILE*+setvbuf(file, nullptr, _IOFBF, 64*1024)设大缓冲区 - 每条日志调用一次
fwrite(log.c_str(), 1, log.size(), file)+fwrite("\n", 1, 1, file),但绝不每条都fflush() - 只在 shutdown、累计满 1MB 或每 100ms 主动
fflush();关键错误才用fsync(fileno(file)),它会阻塞,慎用
后台线程怎么安全退出不丢最后几条日志
主线程退出时,消费者线程若还在 wait() 或 write(),会被直接 kill,队列里未处理的日志就永远不落盘。
- 必须加
std::atomic<bool> shutdown_requested{false}</bool>全局标志 - 消费者循环里用
cv.wait_for(lock, 100ms, [&]{ return shutdown_requested || !queue.empty(); }),避免死等 - 主线程退出前先设
shutdown_requested = true,再cv.notify_one(),然后thread_.join() -
join()前必须确保消费者已清空队列并完成最后一次fflush()——漏掉这步,join()只是等线程结束,不是等日志写完
双缓冲区最易被忽略的点是:buffer 切换必须原子、切换瞬间不能 memcpy、刷盘线程必须只读访问已交换出的 buffer。任何一处引入锁或拷贝,性能瓶颈就从磁盘挪回 CPU,还可能因异常中断导致状态错乱。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











