采用生产者-消费者模型,通过无锁队列实现异步日志,主线程仅内存拷贝,日志线程批量写入文件,结合双缓冲与线程局部存储,降低延迟并提升吞吐。

实现一个高性能的 C++ 多线程日志库,核心目标是:低延迟、高吞吐、线程安全、异步写入、避免阻塞业务线程。下面是一个实用且可扩展的设计方案。
1. 异步日志系统架构
为了避免日志写入磁盘影响主线程性能,采用“生产者-消费者”模型:
- 生产者:各业务线程调用日志接口,将日志消息放入无锁队列。
- 消费者:单独的日志线程从队列中取出消息并写入文件。
这样主线程只需做一次内存拷贝或指针传递,不涉及 I/O 操作,极大降低延迟。
2. 无锁队列设计
使用无锁(lock-free)环形缓冲区(Ring Buffer)作为日志队列,提升多线程并发性能。
立即学习“C++免费学习笔记(深入)”;
推荐使用 boost::lockfree::spsc_queue(单生产者单消费者)或自己实现基于原子操作的 ring buffer。
关键点:
- 每个线程绑定一个日志生产队列,减少竞争。
- 或使用支持多生产者的无锁队列(如 boost::lockfree::queue)。
- 设置最大缓存条数,满时可丢弃或阻塞(根据场景选择)。
3. 日志格式化与缓冲优化
格式化本身较耗时,不能在主线程同步完成。
两种策略:
- 方案一:延迟格式化 —— 生产者只传递原始参数(如 format string + args),消费者线程完成格式化。需序列化参数,实现复杂但性能好。
- 方案二:预格式化 —— 生产者在线程局部缓冲区中快速格式化为字符串,再提交到队列。简单易实现,适合大多数场景。
建议使用 fmt 库 或 std::format(C++20)进行高效格式化。
4. 双缓冲机制(Double Buffering)
进一步减少内存分配和锁竞争。
原理:
- 准备两块缓冲区(A 和 B)。
- 日志线程写入 A 时,生产者写入 B。
- 当 B 满或定时刷新时,交换 A 和 B,日志线程处理新数据,旧缓冲清空重用。
结合线程局部存储(TLS),每个线程独占缓冲区,避免锁。
5. 日志级别与过滤
支持 DEBUG、INFO、WARN、ERROR 等级别,编译期或运行期控制输出。
在生产者端就进行级别过滤,避免无效入队。
示例:
if (level >= global_log_level) { // 进入日志流程 }6. 高效文件写入策略
日志线程写文件时注意:
- 使用带缓冲的文件流(如 setvbuf)。
- 累积一定条数或时间间隔(如每 10ms)批量写入,减少系统调用。
- 支持按大小滚动(log rolling)和按天分割。
- 考虑 mmap 写入(高级优化,跨平台性差)。
7. 性能关键技巧
- 避免动态内存分配:使用对象池管理日志记录对象。
- 使用 thread_local 缓冲区减少锁竞争。
- 时间戳预计算或缓存(高频日志中时间获取也耗时)。
- 启用编译优化,内联关键函数。
8. 简化示例结构
核心类设计:
- Logger:用户接口,提供 info()、error() 等方法。
- LogQueue:无锁队列,存放日志项。
- LogWriter:后台线程,消费日志并写文件。
- LogMessage:日志条目,包含级别、时间、内容等。











