单例模式与阻塞队列实现异步的日志系统

单例模式与阻塞队列实现异步的日志系统

    • 阻塞队列
      • 定义
      • 实现(c++11)
    • 单例模式
      • 定义
      • 类型
      • 懒汉式(编程使用)
      • 饿汉式
      • C++中static对象的初始化
        • non-local static对象(函数外)
        • local static 对象(函数内)
    • 异步写日志

阻塞队列

定义

在多线程编程中阻塞队列是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出

在c++标准库STL中常用的队列容器都不是线程安全的

实现(c++11)

C++11条件变量使用详解
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是std::mutex,并且管理这个锁 只能是 std::unique_lock< std::mutex > RAII模板类。单例模式与阻塞队列实现异步的日志系统_第1张图片

单例模式

定义

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

类型

  1. 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
  2. 懒汉式:真正需要使用对象时才去创建该单例类对象

懒汉式(编程使用)

C++11规定了local static在多线程条件下的初始化行为,要求编译器保证了内部静态变量的线程安全性。
《Effective C++》提出了一种更优雅的单例模式实现,使用函数内的 local static 对象。这样,只有当第一次访问getInstance()方法时才创建实例。
单例模式与阻塞队列实现异步的日志系统_第2张图片
原始的饿汉式单例模式初始化时在多线程条件是不安全的
原因:
如果两个线程同时判断instance为空,那么都会去实例化一个Singleton对象,就变成双例。

	static Singleton* getInstance() 
        {
		if(instance == NULL) //线程不安全
			instance = new Singleton();
		return instance;
	}

c++11之前的解决办法:使用锁(Double Check(双重校验) + Lock(加锁))
注意,线程安全问题仅出现在第一次初始化(new)过程中,而在后面获取该实例的时候并不会遇到,也就没有必要再使用lock。
使用双重校验的原因:单重校验每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象。

   	static Singleton* getInstance() {
   		if (instance == NULL) {
   			Lock lock;  // 基于作用域的加锁,超出作用域,自动调用析构函数解锁
   			if (instance == NULL) {
   				instance = new Singleton();
   			}
   		}
   		return instance;
   	}

饿汉式

由于在main函数之前初始化,所以没有线程安全的问题
潜在问题:no-local static对象(函数外的static对象)在不同编译单元中的初始化顺序是未定义的

static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定,如果在初始化完成之前调用 getInstance() 方法会返回一个未定义的实例。

C++中static对象的初始化

non-local static对象(函数外)

C++规定,non-local static 对象的初始化发生在main函数执行之前,也即main函数之前的单线程启动阶段,所以不存在线程安全问题。但C++没有规定多个non-local static 对象的初始化顺序,尤其是来自多个编译单元的non-local static对象,他们的初始化顺序是随机的。

local static 对象(函数内)

对于local static 对象,其初始化发生在控制流第一次执行到该对象的初始化语句时。多个线程的控制流可能同时到达其初始化语句。

C++11之前,在多线程环境下local static对象的初始化并不是线程安全的。具体表现就是:如果一个线程正在执行local static对象的初始化语句但还没有完成初始化,此时若其它线程也执行到该语句,那么这个线程会认为自己是第一次执行该语句并进入该local static对象的构造函数中。这会造成这个local static对象的重复构造,进而产生内存泄露问题。所以,local static对象在多线程环境下的重复构造问题是需要解决的。

而C++11则在语言规范中解决了这个问题。C++11规定,在一个线程开始local static 对象的初始化后到完成初始化前,其他线程执行到这个local static对象的初始化语句就会等待,直到该local static 对象初始化完成。

异步写日志

  1. 将生产出来的日志信息存储到阻塞队列之中
    在后台开辟一个线程专门用于数据的输出
    异步写日志公有方法,调用私有方法async_write
    不断的从阻塞队列之中取出数据进行写入操作,若无数据可写,则等待通知
  2. 工作线程调用的函数要为静态函数
    在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static
    C++中 线程函数为静态函数 及 类成员函数作为回调函数

你可能感兴趣的:(webserver_jc,单例模式,c++)