C++多线程的Singleton(1)
C++多线程的Singleton(2)
?
Singleton是我使用过的最多的设计模式,也是日常工作中大家会经常用到的设计模式。其实,在C++里面写一个Singleton,不是一件非常容易的事情,以至于《C++设计新思维》里面花了一章内容专门讲解。难点在哪里呢?其实就是两个:
(1)多线程的并发性 (如果你的程序是单线程的,那么就没有这个问题)
(2)生命周期
?
下面我们就开始这个旅程。另外一点,我现在使用C++的原则是KISS,保持设计和代码的简单性。
?
首先,我们先来看看这两个问题。
(1)多线程的并发性,这不难理解。如果在单线程模式下,我们通常会这么写Singleton:
// Singleton.hclass Singleton{ public: ~Singleton(){} static Singleton& getInstance(); private: Singleton(){} };// Singleton.cpp/* static */Singleton& Singleton::getInstance(){ static Singleton *instance = new Singleton; return &instance;} ??
?这种方法是按照《Effective C++》里面的建议,不使用class static变量,而使用函数static变量。这个是延迟初始化,如果整个过程没有函数调用Singleton::getInstance(),那么就不会有Singleton这个对象生成。为什么返回一个Reference,而不是一个指针,是因为这样不容易被误delete。可是如果是多线程呢?有Java经验的同学都会想到下面这个方法:
?
// Singleton.h 错误的实例class Singleton{ public: ~Singleton(){} static Singleton& getInstance(); private: Singleton(){} static Lock lock_;};// Singleton.cpp/* static */Lock Singleton::lock_;Singleton& Singleton::getInstance(){ static Singleton *instance = NULL; if (NULL == instance) // thread B Pos1 { LockHandler handler(lock_); if (NULL == instance) { instance = new Singleton; // thread A Pos2 } } return &instance;} ?(注解:Lock是类似与封装了pthread_mutex_t的类,具有lock(), unlock(), trylock()这样的方法;而LockHandler的构造函数就会调用lock_.lock(),析构函数会调用lock_.unlock(),这是一种RIIA的惯用法)。这段代码非常Java的Singleton代码,它有一个响亮的名字:两次检查。
?
public class Singleton { private Singleton(){} private static Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } }}上面这段Java代码在Java5之后是正确的,在Java5之前,在有些平台上这段代码是有bug的。那C++的两次检查呢?是错误的。知道为什么这段代码是错误的,只会让让你很郁闷,因为instance = new Singleton这句话,根据正常的理解,应该是Singleton分好内存,然后再调用构造函数,然后再把那个地址赋值给instance。可是事实上这是不一定的,有可能先完成这次赋值,再调用构造函数。也许你应该明白为什么有问题了吧。假设threadA到了pos2那个地方,此时threadB刚好来到pos1,然后它发现instance==null不成立,于是threadB就可以使用instance了。可是此时,instance指向的对象还没有正常的调用构造函数。换句话说,threadB使用了一个没有被正确初始化的对象。看到这里也许你要哭了,没错,我也哭了。这不是我们程序员本需要考虑的问题,可是它就是像座山一样在那里。没办法,如果你使用C++,那么请接受它吧。我可以说这种问题很难测试到,也许它会跟随你的系统几个月甚至上年,可是有一天它突然崩溃了,你才知道原来是这样造成的。没办法,那我们退回到一个保守的地方:
?
// Singleton.hclass Singleton{ public: ~Singleton(){} static Singleton& getInstance(); private: Singleton(){} static Lock lock_;};// Singleton.cpp/* static */Lock Singleton::lock_;Singleton& Singleton::getInstance(){ static Singleton *instance = NULL; LockHandler handler(lock_); if (NULL == instance) // thread B Pos1 { instance = new Singleton; // thread A Pos2 } return &instance;} ?这个代码是对的,正确的。可是它有些效率问题,因为你每次访问这个资源的时候,都需要去竞争这个lock_。我们需要继续优化这个。(未完待续)
?