场景
公司有成熟的日志库,但是打印日志的功能是在调用线程里触发的,简而言之就是哪个线程调用了日志库的打印功能,就在哪个线程里输出,如果打印功能频繁,可能会影响该线程的逻辑处理。目前公司的软件界面显示和计算等逻辑很频繁,考虑用异步的方式实现打印。
思路
1 需要把要写的参数封装成一个数据结构然后投递到异步队列里。
2 因为参数类型多变,需要用模板实现写日志的函数。参数个数也不确定,就需要用可变参数模板。
3 需要另一个线程从队列中取数据并打印日志,也可调用第三饭日志库打印。为减少线程调度开销,在队列为空时,需要将队列挂起。
具体实现
日志等级定义为几个级别
1 | enum LogLv { |
定义要投递给队列的任务结构
1 | class LogTask { |
定义异步处理的日志类,先给出全部代码
1 | class AsyncLog : public QThread { |
下面分别解释每段含义
我们先定义了一个单例类
1 | static AsyncLog& Instance(){ |
通过可变参数列表,实现异步写功能
1 | //可变参数列表,异步写 |
AsyncWrite用到了可变参数模板,类型为模板类型的右值引用,这被称作万能引用,使用该引用有几个好处
1 支持右值传递
2 支持完美转发
函数内用到了折叠表达式,折叠表达式基本语法如下
1 | (表达式, ...) |
折叠表达式会循环执行表达式,直到参数列表用完。
AsyncWrite主要实现了将可变参数封装为task然后投递到队列里,如果队列由空到满则需激活消费者线程取消费队列。
如果你的编译器不支持C++17, 可以改为实现两个模板函数TaskEnque入队。
接下来的run函数重载了QThread的run函数,意在启动一个线程处理任务。
processTask 是处理打印日志的功能。
formatString 为多参数模板函数,意在模拟将多个参数匹配和拼接的功能,当然这个函数可替代为你使用的第三方库。
测试和使用
我们在主函数内初始化日志类,然后启动一个界面阻塞主线程,点击关闭界面后,会销毁日志类开辟的线程。
1 | int main(int argc, char *argv[]) |
函数运行后输出如下
1 | formatstr is "hello world ! my name is zack fair " |
总结
本文介绍了如何用模板和可变参数构造一个异步打印功能的类,是基于QT实现的,之后又实现了C++标准版本的。源码链接如下