Web服务器---TinyWebServer代码详细讲解(log模块)
log是日志模块,一个合格的服务器当然少不了日志来记录错误异常等等信息。我们想设计一个日志模块,他能顺利写日志但是又不要占用主线程时间去写,所以我们设计异步写日志的模块。
基础知识
日志,由服务器自动创建,并记录运行状态,错误信息,访问数据的文件。
同步日志,日志写入函数与工作线程串行执行,由于涉及到I/O操作,当单条日志比较大的时候,同步模式会阻塞整个处理流程,服务器所能处理的并发能力将有所下降,尤其是在峰值的时候,写日志可能成为系统的瓶颈。
生产者-消费者模型,并发编程中的经典模型。以多线程为例,为了实现线程间数据同步,生产者线程与消费者线程共享一个缓冲区,其中生产者线程往缓冲区中push消息,消费者线程从缓冲区中pop消息。
阻塞队列,将生产者-消费者模型进行封装,使用循环数组实现队列,作为两者共享的缓冲区。
异步日志,将所写的日志内容先存入阻塞队列,写线程从阻塞队列中取出内容,写入日志。
单例模式,最简单也是被问到最多的设计模式之一,保证一个类只创建一个实例,同时提供全局访问的方法。
单例模式
单例模式作为最常用的设计模式之一,保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
实现思路:私有化它的构造函数,以防止外界创建单例类的对象;使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
单例模式有两种实现方法,分别是懒汉和饿汉模式。顾名思义,懒汉模式,即非常懒,不用的时候不去初始化,所以在第一次被使用时才进行初始化;饿汉模式,即迫不及待,在程序运行时立即初始化。
经典的线程安全懒汉模式
1class single{
2private:
3 //私有静态指针变量指向唯一实例
4 static single *p;
5
6 //静态锁,是由于静态函数只能访问静态成员
7 static pthread_mutex_t lock;
8
9 //私有化构造函数
10 single(){
11 pthread_mutex_init(&lock, NULL);
12 }
13 ~single(){
}
14
15public:
16 //公有静态方法获取实例
17 static single* getinstance();
18
19};
20
21pthread_mutex_t single::lock;
22
23single* single::p = NULL;
24single* single::getinstance(){
25 if (NULL == p){
26 pthread_mutex_lock(&lock);
27 if (NULL == p){
28 p = new single;
29 }
30 pthread_mutex_unlock(&lock);
31 }
32 return p;
33}
为什么要用双检测,只检测一次不行吗?
如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。
局部静态变量之线程安全懒汉模式
前面的双检测锁模式,写起来不太优雅,《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用函数内的局部静态对象,这种方法不用加锁和解锁操作。
1class single{
2private:
3 single(){
}
4 ~single(){
}
5
6public:
7 static single* getinstance();
8
9};
10
11single* single::getinstance(){
12 static single obj;
13 return &obj;
14}
如果使用C++11之前的标准,还是需要加锁,这里同样给出加锁的版本
1class single{
2private:
3 static pthread_mutex_t lock;
4 single(){
5 pthread_mutex_init(&lock, NULL);
6 }
7 ~single(){
}
8
9public:
10 static single* getinstance();
11
12};
13pthread_mutex_t single::lock;
14single* single::getinstance(){
15 pthread_mutex_lock(&lock);
16 static single obj;
17 pthread_mutex_unlock(&lock);
18 return &obj;
19}
饿汉模式
饿汉模式不需要用锁,就可以实现线程安全。原因在于,在程序运行时就定义了对象,并对其初始化。之后,不管哪个线程调用成员函数getinstance(),都只不过是返回一个对象的指针而已。所以是线程安全的,不需要在获取实例的成员函数中加锁。
1class single{
2private:
3 static single* p;
4 single(){
}
5 ~single(){
}
6
7public:
8 static single* getinstance();
9
10};
11single* single::p = new single(