再探线程安全的单例模式实现

最近在看陈硕的moduo,里面提到了线程安全的单例实现,不由想到我之前写的c++单例模式的几种实现,按moduo中的说法,按照cpu指令乱序执行的影响,因此DCL(double check locking)是靠不住的,这里陈硕提出了一种基于pthread_once的单例模式的实现。

单例模式的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template<typename T>
class Singleton : boost::noncopyable
{
public:
static T& instance(){
pthread_once(&ponce_, &Singleton::init);
return *value_;
}
private:
Singleton();
~Singleton();
static void init(){
value_ = new T();
}
private:
static pthread_once_t ponce_;
static T* value_;
}
/// 必须在头文件中声明static变量
template<typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;

template<typename T>
T* Singleton<T>::value_ = nullptr;

缺点

这个Singleton倒是没有用到之前的各种加锁技巧等等,用pthread_once_t来保证lazy-initialization的线程安全。线程安全性由pthreads库来保证。使用方式也比较简单:

Foo& foo = Singleton<Foo>::instance();

这个Singleton没有考虑对象的销毁,作者给出的理由是在长时间稳定运行的服务器上没有必要销毁这个对象,在短时间运行的场景下,程序退出自然对象就被释放。个人感觉是因为析构函数确实很难设计,按照目前的写法,析构函数是私有的,自然无法被访问,对象也就无法被析构,但是如果把析构函数变成公有的,那么就会出现一个问题,如果用户不小心调用了析构函数,那么这个单例模式就没有用了,因为唯一的对象已经被销毁,同时也绝对无法创建新的对象,这个时候就比较尴尬了,这可能是作者没有对析构给出好的解决办法的一个原因。另外,这个Singleton只能调用默认构造函数,如果需要定制构造方式的话,作者认为可以用模版特化,我个人觉得可以直接不实用模版就好了,倒不算什么问题。