[TOC]
日志系统是服务器端的常见模块,服务器在运行时一般会自动创建一些日志,用来记录自身的运行状态、错误信息和访问数据的文件等。本文考虑的是一个支持同步/异步的日志系统。
本文所讲的日志系统涉及到的知识有可变参数宏+单例模式+阻塞队列(互斥锁+条件变量+生产者-消费者模型),但是不再讲解单例模式和阻塞队列,其相关知识可以参考作者的其他博客。
一、可变参数宏
日志输出的文本信息一般是要分level的,常见的如debug,info,warning,error四种级别。如何让我们的日志系统在编码中插入十分方便呢?答案就是使用可变参数宏。
__VA_ARGS__
是一个可变参数的宏,定义时宏定义中参数列表的最后一个参数为省略号
,在实际使用时会发现有时会加##
,有时又不加。
//最简单的定义
#define LOG_INFO(...) printf(__VA_ARGS__)
#define LOG_INFO(...) printf(##__VA_ARGS__)//推荐
//搭配__VA_ARGS__的format使用
#define LOG_INFO(format, ...) printf(format,__VA_ARGS__)
#define LOG_INFO(format, ...) printf(format,##__VA_ARGS__)//推荐
二、日志系统的流程图
- 本文的日志系统的流程图如下:
可以看到,上图中关键逻辑如下。
日志文件
- 局部变量的懒汉模式获取实例
- 生成日志文件,并判断同步和异步写入方式
同步
- 判断是否分文件
- 直接格式化输出内容,将信息写入日志文件
异步
- 判断是否分文件
- 格式化输出内容,将内容写入阻塞队列,创建一个写线程,从阻塞队列取出内容写入日志文件
三、日志类的实现
在多线程中,需要考虑共享资源的安全性,使用互斥锁
(1)单例模式的日志类
单例模式有两种实现方法,分别是懒汉模式和饿汉模式。顾名思义,。
#include<iostream>
#include<pthread.h>
using namespace std;
class Singleton{
protected:
//构造函数被保护,不能被外界访问
Singleton(){
pthread_mutex_init(&lock, NULL);
cout<<"构造函数执行成功"<<endl;
};
private:
//设置一个私有的静态类指针,用来保存全局唯一的实例对象
static Singleton* _instance;//静态成员变量
static pthread_mutex_t lock;//静态锁,是由于静态函数只能访问静态成员
public:
//设置一个公有的静态成员函数,用来作为外界唯一的创建接口
static Singleton* Instance(){
if (_instance==NULL){
pthread_mutex_lock(&lock);
if (_instance==NULL){
_instance = new Singleton();
}
pthread_mutex_unlock(&lock);
}
return _instance;
};
};
// 全局变量类外声明,直接实例化
Singleton* Singleton::_instance=NULL;
pthread_mutex_t Singleton::lock;
为什么要用双检测,只检测一次不行吗?
如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。
(2)局部静态变量之线程安全懒汉模式
前面的双检测锁模式,写起来不太优雅,《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用函数内的局部静态对象,这种方法不用加锁和解锁操作。它不用加锁是因为在C++11之后,编译器会保证局部静态变量的线程安全性,所以不需要程序员额外为局部静态变量设置线程安全性。
class Singleton{
protected:
//构造函数被保护,不能被外界访问
Singleton(){cout<<"构造函数执行成功"<<endl;};
public:
//设置一个公有的静态成员函数,用来作为外界唯一的创建接口
static Singleton* Instance(){
static Singleton _instance;//静态成员变量
return &_instance;
};
};
二、阻塞队列的介绍
阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里获取元素的线程。阻塞队列(block queue)是一种支持阻塞的插入和移除的队列,用于多线程或多进程同步的情景。阻塞队列使用的是生产者-消费者模式,生产者指的是向队列中添加元素的线程,消费者是从队列中获取元素的线程。
- 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
- 支持阻塞的移除方法:意思是队列为空时,获取元素(同时移除元素)的线程会被阻塞,等到队列变为非空。
和阻塞队列相关的知识点有:互斥锁+条件变量+生产者-消费者模型。
有关阻塞队列的介绍,可以参考读者的其他文章。