直接用std::ofstream每次写日志性能差,因频繁打开/关闭文件、同步刷盘及字符串拼接消耗大量cpu和i/o资源,高并发下open()与operator

为什么不能直接用 std::ofstream 每次写日志?
因为频繁打开/关闭文件、同步刷盘、字符串拼接都会吃掉大量 CPU 和 I/O 时间。实测在高并发场景下,std::ofstream::open() + operator 组合可能让吞吐量掉到 1k QPS 以下,且容易阻塞主线程。
关键问题不在“怎么写”,而在“怎么避免写的时候卡住程序”。所以滚动日志类必须做到:异步缓冲、预分配内存、原子切换文件句柄、零拷贝写入。
- 日志写入路径必须绕过
std::ostream的格式化开销,直接操作char*缓冲区 - 滚动时机不能依赖
stat()查大小——太慢,应由写入计数器触发 - 多线程写日志时,
std::atomic控制的 ring buffer 比锁更轻量;但 buffer 切换(如 rollover)仍需一次轻量锁
如何设计线程安全的双缓冲 + 原子切换?
核心是两个 std::vector<char></char> 缓冲区(buf_a 和 buf_b),一个用于写入,一个用于落盘。用 std::atomic<bool></bool> 标记当前活跃 buffer,写线程只往 active buffer 填数据,当它满或触发滚动时,原子交换指针并唤醒 flush 线程。
示例关键逻辑:
std::atomic<bool> buf_a_active{true};
std::vector<char> buf_a, buf_b;
std::mutex rollover_mutex;
<p>void write(const char* data, size_t len) {
auto& buf = buf_a_active ? buf_a : buf_b;
if (buf.size() + len > buf.capacity()) {
std::lock_guard<:mutex> lk(rollover_mutex);
// 触发 flush + 滚动 + 重置 inactive buffer
flush_inactive();
buf_a_active = !buf_a_active;
reset_buffer(buf_a_active ? buf_b : buf_a);
}
buf.insert(buf.end(), data, data + len);
}</:mutex></p></char></bool>
- 不要用
std::stringstream构造日志行——它会反复 realloc,改用snprintf直接写入预分配 buffer - buffer 容量建议设为 64KB 或 128KB:太小导致频繁切换,太大增加延迟毛刺
-
flush_inactive()必须用writev()或::write()直接 syscall,跳过 C 库缓冲
滚动文件名怎么生成才不冲突、不丢日志?
别用 strftime() + gettimeofday() 拼时间戳——在毫秒级高频写入下,同一秒内多个文件名会重复。正确做法是:用单调递增序号 + 固定前缀 + 当前日期,例如 app_20240520.001.log。
- 序号存在
std::atomic<int></int>里,每次 rollover 自增,无需锁 - 日期只在每天首次 rollover 时更新,用
time_t+localtime_r()校验,避免跨天时错乱 - 旧日志清理交给外部脚本或启动时扫描
app_*.log,类本身不负责删除——否则unlink()可能阻塞
为什么 std::this_thread::yield() 比 usleep(1) 更适合等待 flush 完成?
当写线程发现 buffer 满,而 flush 线程还没处理完上一轮,此时若休眠固定微秒,可能过长(浪费)或过短(忙等)。yield() 让出当前调度权,但不指定时长,OS 决定何时再调度该线程,对 latency 更友好。
- 仅在 buffer 切换后、且新 buffer 仍不可用时调用
yield(),不是每次写都 yield - 如果 flush 线程卡住(如磁盘满),需要超时机制:记录上次 flush 时间戳,超过 500ms 就强制丢弃日志并报错
- 务必用
std::atomic_flag标记 flush 状态,而不是轮询size()——后者可能被编译器优化掉
最易被忽略的是 flush 线程的优先级:默认是 normal,但在嵌入式或实时系统中,应设为 SCHED_FIFO 并提升优先级,否则 buffer 积压会雪崩。这点没法靠类封装解决,得靠部署时配置。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











