浅析单例模式与线程安全(Linux环境c++版本)

什么是单例模式

单例模式是设计模式中一种常用模式,定义是Ensure a class has only one instance, and provide a global point of access to it.(确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例)

用《设计模式之禅》里的话说,就是,在一个系统中,要求一个类有且仅有一个对象,如果出现多个就会出现“不良反应”,比如以下情景可能会用到单例模式

单例模式的优点

单例模式实现单线程实现

说了这么多,但是模式如何设计?

为了避免后面的派生类继承这个类,误将该类多次初始化(即内存中多个备份),我们应该将其构造函数设为私有,并且把唯一的一次初始化留到静态函数中,比如c++写出来是这样的

#include <iostream>template<typename T>class Singleton{public:static T& instance(){if (flag_init){flag_init = false;flag_destory = true;init();}return *value_;}static void init(){value_ = new T();}static void destory(){if (flag_destory){flag_destory = false;suicide();}}static void suicide(){delete value_;}private: static T* value_;       //只进行一次的初始化的指针static bool flag_init;     //表明是否可以进行初始化的标志位static bool flag_destory; //表明是否进行过销毁的标志位Singleton(){}            //私有函数,导致无法多次初始化这个类~Singleton(){}           //私有函数,导致无法多次销毁这个类};//静态变量必须在外面初始化template<typename T>T* Singleton<T>::value_ = NULL;template<typename T>bool Singleton<T>::flag_destory = false;template<typename T>bool Singleton<T>::flag_init = true;//测试单例是否可用的测试类class Example{public:Example(){value = 0;}~Example(){}void tool(){value++;std::cout << value<< std::endl;}private:int value;};int main(int argc, char *argv[]){Example& ex1 = Singleton<Example>::instance();ex1.tool();Example& ex2 = Singleton<Example>::instance();ex2.tool();Singleton<Example>::destory();Singleton<Example>::destory();return 0;}输出两次分别是1 和 2 表示操纵的是同一个对象,内存中只有一份,多次销毁,无效的销毁也会被忽略多线程实现

但是这样就行了吗,如果在多线程会怎么样, 如果是多线程的话,在同时修改创建或者销毁的两个bool值时就可能发生错误。

那么怎么解决呢,有人提出了DCLP(double checked locking pattern)机制,结合互斥锁mutex,询问两次的方法。

分析:

当多个线程同时进入instance()时,都会发现第一个if (value_ == NULL)为真,之后开始竞争,拿到锁的线程会直接通过第二个if (value_ == NULL)进行初始化,然后释放锁,其他的线程拿到之后会到达第二个if ,此时实例已经被初始化,直接返回实例,不会进行二次初始化

实现如下

<span style="font-size:12px;">#include <iostream>#include <unistd.h>#include "Mutex.h"template<typename T>class Singleton{public:static T& instance(){if (value_ == NULL){MutexLockGuard guard(mutex_); //这个表示区域锁,实现后附代码,原理参见《Linux多线程服务端编程》陈硕著if (value_ == NULL){init();}}return *value_;}static void init(){value_ = new T();}private: static T* value_;static MutexLock mutex_;Singleton(){}~Singleton(){}};//静态变量必须在外面初始化template<typename T>T* Singleton<T>::value_ = NULL;template<typename T>MutexLock Singleton<T>::mutex_;//测试单例是否可用的测试类class Example : boost::noncopyable{public:Example(){value = 0;}~Example(){}void tool(){value++;std::cout << value<< " ";}private:int value;};void* thread(void*arg){Example& ex3 = Singleton<Example>::instance();ex3.tool();return NULL;}int main(int argc, char *argv[]){pthread_t tid;pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);pthread_create(&tid, NULL, thread, NULL);Example& ex1 = Singleton<Example>::instance();ex1.tool();sleep(1);return 0;}</span>编译时记得加参数。。。-pthread

Mutex.h代码

运行检测,好像多线程也没什么问题,但是这样真的可以吗,国外大神过来打脸了

meyers大神的一篇文章

meyers指出new Singleton,这步(也就是init()函数里的new)在真正运行时会分解成三个行为

分配内存

构造实例对象

将指针指向分配的内存

这三个指令可能会被CPU重排,然后执行顺序发生变化比如 3->1->2

有时我们选择改变,并非经过深思熟虑,

浅析单例模式与线程安全(Linux环境c++版本)

相关文章:

你感兴趣的文章:

标签云: