今天学习了一下c++中的singleton。google了一篇论文C++ and the Perils of Double-Checked Locking。大名鼎鼎的Scott Meyers写的。论文使用c++讲解,看了之后受益匪浅。
巧的是,读完之后刚好看见http://coolshell.cn酷壳站长陈皓大哥的一篇文章http://blog.csdn.net/haoel/article/details/4028232也是讲的这个问题。不同于上面那篇文论,陈皓大哥用的是java讲解。
我想做的是,还原一下那篇论文,算是一个学习总结吧。也是对陈皓大哥那篇文章的一个补充吧。
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》
你去google一下设计模式这四个字,肯定有提到单例(singleton)这个经典设计模式。but but but,,,,,传统的单例模式实现不是线程安全的!!!
为了解决线程安全问题,程序员们做了很多努力,目前最流行的就是double-checked locking pattern(dclp). dclp可为共享资源(比如singleton)添加有效的线程安全。
but but but DCLP也有不足之处,比如不可复用,不能方便地移植,并且在多核处理器系统中也起不到作用。今儿不说这些不足,只说说DCLP怎么解决线程安全问题的。
单例和多线程
传统的单例模式实现:
class Singleton { private: Singleton(){} public: static Singleton* instance() { if(_instance == 0) { _instance = new Singleton(); } return _instance; } private: static Singleton* _instance; public: int atestvalue; }; Singleton* Singleton::_instance = 0;
稍微分析一下:
1. 例如线程A进入函数instance执行判断语句,这句执行后就挂起了,这时线程A已经认为_instance为NULL,但是线程A还没有创建singleton对象。
2. 又有一个线程B进入函数instance执行判断语句,此时同样认为_instance变量为null,因为A没有创建singleton对象。线程B继续执行,创建了一个singleton对象。
3. 稍后,线程A接着执行,也创建了一个新的singleton对象。
4. fuck!!两个对象!
从上面分析可以看出,需要对_instance变量加上互斥锁:
Singleton* Singleton::instance() { Lock lock; // acquire lock (params omitted for simplicity) if (_instance == 0) { _instance = new Singleton; } return _instance; } // release lock (via Lock destructor)
Singleton* Singleton::instance() { if (<span style="font-family:CMBX12;">_instance</span> == 0) { // 1st test Lock lock; if (<span style="font-family:CMBX12;">_instance</span> == 0) { // 2nd test <span style="font-family:CMBX12;">_instance</span> = new Singleton; } } return <span style="font-family:CMBX12;">_instance</span>; }
我们来仔细打量一下这句代码:
_instance = new singleton()为了执行这句代码,机器需要做三样事儿:
1.singleton对象分配空间。
2.在分配的空间中构造对象
3.使_instance指向分配的空间
遗憾的是编译器并不是严格按照上面的顺序来执行的。可以交换2和3.
将上面三个步骤标记到代码中就是这样:
Singleton* Singleton::instance() { if (<span style="font-family:CMBX12;">_instance</span> == 0) { Lock lock; if (<span style="font-family:CMBX12;">_instance </span>== 0) { <span style="font-family:CMBX12;">_instance </span>= // Step 3 operator new(sizeof(Singleton)); // Step 1 new (<span style="font-family:CMBX12;">_instance</span>) Singleton; // Step 2 } } return <span style="font-family:CMBX12;">_instance</span>; }
貌似这时无法解决的问题了,咋办呢。搞嵌入式的程序员可能想到用c++中的volatile关键字。对,就是用volatile,但是用volatile就要一用到底,用了之后就是下面这种丑陋的代码了。
class Singleton { public: static volatile Singleton* volatile instance(); ... private: // one more volatile added static<span style="font-family:CMBX12;"> </span>Singleton* volatile <span style="font-family:CMBX12;">_instance</span>; }; // from the implementation file volatile Singleton* volatile Singleton::<span style="font-family:CMBX12;">_instance</span> = 0; volatile Singleton* volatile Singleton::instance() { if (<span style="font-family:CMBX12;">_instance </span>== 0) { Lock lock; if (<span style="font-family:CMBX12;">_instance </span>== 0) { // one more volatile added Singleton* volatile temp = new<span style="font-family:CMBX12;"> </span>Singleton; <span style="font-family:CMBX12;">_instance </span>= temp; } } return <span style="font-family:CMBX12;">_instance</span>; }