设计模式之单例模式(C++)
来源:互联网 发布:nodejs nginx 部署 编辑:程序博客网 时间:2024/05/16 11:53
设计模式之单例模式(C++实现)
1. 什么是Singleton?
设计模式中的Singleton,中文翻译是单例模式(也有翻译单件模式),指的是限制一个类只能实例化出一个对象。这种看是奇特使用类的方式在某些情况下是有用的,比如整个系统中只有一个状态管理的类,又比如在Windows中的任务管理器。这些应用场景下只需要一个类的实例,多个实例反而不方便管理,也可能存在某些潜在的冲突,在这些应用中Singleton就可以很好地得到应用。
2. 替代方案
Singleton很明显可以使用一个全局的变量来替换(C++),但是相比于全局变量它有以下的优势:
可以避免引入与全局变量产生冲突
使用单例模式可以使用到延迟加载(lazy allocation and initialization),在没有使用的时候可以节约资源
3. C++中实现
单例模式的一种常用实现如下:
1)考虑到需要只有一个实例,因此常常使用一个静态的变量来表示;
2)为了避免类的使用者new出多个实例,需要将构造函数声明为private
3)但是需要有一种可以得到这个对象的方式,一般使用getInstatnce()这个public的方法来获取这个实例
综合上述介绍,一个简单的实现如下 1.
//定义class Singleton {public: static Singleton* getInstance(); void doSomething();protected: Singleton();private: static Singleton* _instance;};//实现Singleton* Singleton::_instance = nullptr;Singleton* Singleton::getInstance() { if (_instance == nullptr) { _instance = new Singleton; } return _instance;}void Singleton::doSomething() { std::cout << "Doing" << std::endl;}
这个实现在单线程的环境下工作的很好,但是在多线程环境中可能存在着潜在的危险,考虑到两个线程同时运行到 if (_instance == nullptr),其中一个线程在这个时候被挂起,当另一个线程初始化_instance之后,唤醒挂起的线程,这时候该线程会继续创建一个实例,这与Singleton中单个实例的设计背道而驰了。
4. 解决方案
4.1 简单实现
既然上述的设计并非是线程安全的,那么我们在构造_instance的时候给它加锁不就好了吗?实现如下:
//定义class Singleton {public: static Singleton* getInstance(); void doSomething();protected: Singleton();private: static Singleton* _instance;};//实现Singleton* Singleton::_instance = nullptr;std::mutex mutex;Singleton* Singleton::getInstance() { // 加上mutex使得每次只有一个线程进入 std::lock_guard<std::mutex> locker(mutex); if (_instance == nullptr) { _instance = new Singleton; } return _instance;}void Singleton::doSomething() { std::cout << "Doing" << std::endl;}
这样又会引入一个新的令人不爽的地方,线程在getInstance调用时都需要等待其他线程完成访问,这样不利于多线程发挥出它应有的优势。仔细看一下实现,发现实际上真正需要加锁的只有new这一个步骤,这样在初始化完成之后所有的线程就不会在进入到if这个分支中了,于是我们修改为:
Singleton* Singleton::getInstance() { if (_instance == nullptr) { // 加上mutex使得每次只有一个线程进入 std::lock_guard<std::mutex> locker(mutex); _instance = new Singleton; } return _instance;}
但是这样做有一个潜在的危险,当线程A和B都运行到if(_instance == nullptr)之后A线程挂起,B线程运行到加锁,new出对象,之后A线程被唤醒,它也会加锁并再次创建一个对象,于是又会出现两个实例,这与单例模式相悖。
4.2 DCLP方式
既然还是会创建新的对象,那么我们在创建之前再次判断一下不就好了吗,于是引出新的一个概念称为 The Double-Checked Locking Pattern(DCL
P),具体实现如下:
Singleton* Singleton::getInstance() { if (_instance == nullptr) { // 加上mutex使得每次只有一个线程进入 std::lock_guard<std::mutex> locker(mutex); if (_instance == nullptr) _instance = new Singleton; } return _instance;}
接着上文的叙述,在A线程加锁之后发现B线程已经将_instance实例化了,于是_instance == nullptr为false,这样A就不会再次new出实例了,完美的解决了问题。
4.3 新的麻烦
使用DCLP就可以解决这个问题吗?事实上在某种程度上可以,但是不同的编译器在执行getInstance这个函数的时候有不同的处理方式,使得使用DCLP并不是一个适用于所有平台和编译器的完美解决方案,具体的细节十分复杂,读者可以参考下面这篇论文
C++ and the Perils of Double-Checked Locking2
文中给出了一个实现真正线程安全的一个实现(需要针对不同的操作系统实现),实现的模式是:
Singleton* Singleton::getInstance() { Singleton* tmp = m_instance; ... // insert memory barrier if (tmp == NULL) { Lock lock; tmp = m_instance; if (tmp == NULL) { tmp = new Singleton; ... // insert memory barrier m_instance = tmp; } } return tmp;}
4.4 C++11的实现
C++11的一大亮点是引入了跨平台的线程库,通过线程库可以很好的完成上文中提到的问题。
4.4.1 实现方式一
这种实现方式实际上就是对上文中的DCLP的描述,实现代码如下:
std::atomic<Singleton*> Singleton::m_instance;std::mutex Singleton::m_mutex;Singleton* Singleton::getInstance() { Singleton* tmp = m_instance.load(std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_acquire); if (tmp == nullptr) { std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release); m_instance.store(tmp, std::memory_order_relaxed); } } return tmp;}
另外还有其他的实现方式,更多的内容可以参考:Double-Checked Locking Is Fixed In C++11 3
4.4.2 推荐的实现方式
对于singleton的实现方式,在Effective C++中作者实现了
class S{ public: static S& getInstance() { static S instance; return instance; } private: S() {}; S(S const&) = delete; void operator=(S const&) = delete;};
这种实现方式是线程安全的(C++11),在C++11的规范中有一下描述:
The C++11 standard §6.7.4:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
也就是说C++11保证了静态变量在被初始化的时候强制线程保持同步,这种方式就实现了无锁完美的方案,具体内部的实现是编译器的事情,它可能使用DCLP的方式或者其他的方式完成。但是C++11提到的这个语义保证了单例模式的实现。
更多的内容可以参考4
- https://msdn.microsoft.com/en-us/library/ee817670.aspx ↩
- http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf ↩
- http://preshing.com/20130930/double-checked-locking-is-fixed-in-cpp11/ ↩
- http://stackoverflow.com/questions/1008019/c-singleton-design-pattern ↩
- (C#)设计模式 之 单例模式
- 设计模式之单例模式(C++)
- C#--设计模式之单例模式
- Objective-C 设计模式之单例
- Objective-C之单例设计模式
- 设计模式之单例模式(C++)
- C语言和设计模式(之单例模式)
- Objective-C中的设计模式之单例模式
- 设计模式之 单例设计模式
- 设计模式之 单例设计模式
- 设计模式之单例设计模式
- 设计模式之-----------单例设计模式
- 设计模式之:单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 设计模式之单例设计模式
- 【黑马程序员】---- Java 基础之 学习使用API文档
- Android中应用的字体Typeface的设置
- java 动态编译
- SPOJ NUMOFPAL - Number of Palindromes 水题 (Palindromic Tree 练习)
- [github] github入手教程
- 设计模式之单例模式(C++)
- CountDownLatch的使用方法
- iphone常用控件之UIScrollView
- IOS中NSUserDefaults的用法(轻量级本地数据存储)
- poj 3667
- 队列
- 最大公约数和最小公倍数
- 对C++中派生类的认识
- 字符串分割(C++)