单例模式
单例模式的精髓是一个类只产生一个实例对象,当对象被构造以后,阻止构造、拷贝构造、赋值和析构操作。
单例模式可以考虑以下问题,如线程安全性和如何优化等。本文讨论学习单例模式过程中通过博客和书籍学习到的解决方案。
单线程(饿汉式)
保证类仅有一个实例,并提供一个访问它的全局访问点。
由于这种设计方案使用了静态成员,类产生时就创建好了实例对象,也被称作饿汉模式,是一种
空间换时间的做法。同时饿汉式也是线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Singleton { private: Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&);
public: static Singleton& getInstance() { static Singleton obj_; return obj; }
~Singleton(); };
|
C++11中局部静态对象保证了线程线程安全性。使用方法:Singleton &instance_ = Singleton::getInstance();
多线程(懒汉模式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Singleton { private: static Singleton* p_instance; static std::mutex m_mutex; ...
public: static Singleton& getInstance() { if(p_instance == nullptr) { std::unique_lock<std::mutex> ulk(mutex_) if(p_instance == nullptr) { pInstance_ = new Singleton(); } } return *pInstance; }
static void deleteInstance() { delete p_instantce; p_instance = nullptr; } };
Singleton *Singleton::pInstance_ = nullptr; std::mutex Singleton::mutex_;
|
但是双重检查锁存在cpu对指令重排,乱序造成的风险。学习C++的内存模型的过程参考博客https://www.cnblogs.com/haippy/p/3412858.html,得知new
的过程中,cpu顺序执行是先调用malloc分配内存空间,再调用构造函数实例化对象,然后将p_instance指向刚分配的内存地址。在这个过程中,如果指令重排,在初始化之前让p_instance指向刚分配的内存,这样另外一个线程会造成错误的判断。
muduo做法
陈硕在书中谈到这个问题,并使用pthread_once来实现,Linux的threads库保证它指定的函数只执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| template<typename T> class Singleton /*: boost::noncopyable 如果使用boost库就避免写拷贝和赋值构造函数*/ { public: static T& getInstance() { pthread_once(&ponce, &Singleton::init); return *pInstance_; }
static void destory() { delete pInstance_; pInstance_ = NULL; }
private: Singleton(); ~Singleton(); Singleton(const Singleton&); Singleton<T>& operator=(const Singleton&);
static void init() { pInstance_ = new T(); }
private: static pthread_once_t ponce_; static T* pInstance_; };
template<typename T> pthread_once_t Singleton<T>::ponce = PTHREAD_ONCE_INIT;
template<typenmame T> T* Singleton<T>::pInstance = NULL;
|