同步 std::ofstream 写日志拖慢主业务,因每次调用均触发磁盘 I/O 等待;应通过无锁环形缓冲区+独立写线程解耦日志生成与落盘,关闭流同步、避免

为什么同步 std::ofstream 写日志会拖慢主业务?
因为每次 log.info("user login") 都触发一次磁盘 I/O,哪怕只是追加几十字节,也要等内核完成缓冲、刷盘、返回确认。在高并发场景下,线程可能卡在 write() 系统调用上几十毫秒,直接拉高 P99 延迟。
真正耗时的不是格式化字符串,而是 I/O 等待。所以核心思路是:把「日志内容生成」和「日志落盘」彻底解耦。
用无锁环形缓冲区 + 独立写线程实现异步写入
不依赖第三方库(如 Boost.Lockfree),用 std::atomic 和内存序控制生产者/消费者边界,避免互斥锁成为瓶颈。
- 生产者(业务线程)只做两件事:格式化日志到栈上 buffer,然后原子地 push 到环形队列 —— 这个操作通常在 100ns 级别
- 消费者(单个专用线程)循环 pop 日志条目,批量 write 到
std::ofstream,并按需 rotate 文件 - 环形缓冲区大小建议设为 216 ~ 218,太小容易丢日志,太大增加 cache miss
- 必须处理缓冲区满的情况:
if (queue.full()) { drop_count.fetch_add(1, std::memory_order_relaxed); return; },不能阻塞或重试
std::ofstream 的性能陷阱与绕过方式
默认构造的 std::ofstream 启用了 std::ios_base::sync_with_stdio(true),会和 C stdio 同步,带来额外开销;且每次 都检查流状态,频繁调用 write() 也触发多次系统调用。
立即学习“C++免费学习笔记(深入)”;
- 关闭同步:
file.rdbuf()->pubsetbuf(nullptr, 0); file.imbue(std::locale::classic()); - 避免用
拼接,改用file.write(buf.data(), buf.size())直接写入已格式化好的std::string_view - 启用
O_DIRECT(Linux)或FILE_FLAG_NO_BUFFERING(Windows)需谨慎:它要求地址对齐、长度对齐,反而容易因小写入变慢;日常建议保持 page cache,靠内核延迟刷盘 - 每写入 4KB~64KB 再
file.flush()一次,比每条 flush 快 10x+
如何安全地在多线程中复用 std::string 缓冲区?
不能让多个线程共用同一个 std::string 实例去 format,否则需要锁;也不能每次都 new/delete —— 分配器竞争又成瓶颈。
推荐方案是每个线程绑定一个 TLS(thread_local)buffer:
thread_local static std::string tls_buffer; tls_buffer.clear(); tls_buffer.reserve(512); // ... 格式化进 tls_buffer queue.push(tls_buffer.c_str(), tls_buffer.size());
注意:tls_buffer.c_str() 返回的是临时指针,必须在 push 时立即拷贝内容(比如 ring buffer 存的是 char* + size_t,内部 memcpy)。否则 TLS buffer 下次被覆盖,消费者读到的就是垃圾数据。











