一、使用pthread_once
实现起来相对容易,弊端也很明显:不具备可移植性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
pthread_once_t once_control = PTHREAD_ONCE_INIT;
template <class T>
class Singleton {
public:
static T* instance() {
pthread_once(&once_control, &Singleton::init);
return instance_;
};
private:
static void init() {
instance_ = new T();
}
private:
static T* instance_;
};
template <class T>
T* Singleton<T>::instance_ = NULL;
|
二、著名的DCLP
Double-Checked Locking Pattern,双重检查锁定模式。
这种方法使用了锁来保证在多线程环境下可以正常工作,
同时使用了两次检查的方式进行了优化,使得在绝大多数情况下并不需要访问锁。
一种很常见的实现方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Singleton {
public:
static T* instance() {
if (NULL == instance_) { // 第一次检查
Lock lock; // 伪代码
if (NULL == instance_) // 第二次检查
instance_ = new T;
}
return instance_;
};
private:
static T* instance_;
};
template <class T>
T* Singleton<T>::instance_ = NULL;
|
这种看起来正确且效率足够高的实现方式,当考虑到编译器的优化时,实际上是有问题的。
分析代码的第8行,这一行 new 的代码至少包含了三个操作:
- 为 class T 分配内存
- 在分配的内存上调用 class T 的构造函数
- 内存地址赋值给 instance_
其中操作2、操作3,是有可能被编译器优化乱序的,即先执行操作3,再执行操作2。
考虑线程 A 执行了操作1、操作3后时间片结束进入休眠,此时 instance_ 并不为 NULL;
线程 B 也进入了 instance() 函数中,返回 instance_,但此时操作2却并未被执行到,
因此线程 B 也就得到了一个未执行构造函数的对象指针,其行为是未定义的。
由于旧标准中缺乏对代码执行次序的约束功能和 memory order 的概念,
因此也决定了在 C++ 11 以前无法实现可移植的线程安全单例模式。
一种可行的方法是利用 boost 的 atomic 库:
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
|
template <class T>
class Singleton {
public:
static T * instance()
{
T * tmp = instance_.load(boost::memory_order_consume);
if (!tmp) {
boost::mutex::scoped_lock lock(mutex_);
tmp = instance_.load(boost::memory_order_consume);
if (!tmp) {
tmp = new T;
instance_.store(tmp, boost::memory_order_release);
}
}
return tmp;
}
private:
static boost::atomic<T *> instance_;
static boost::mutex mutex_;
};
template <class T>
boost::atomic<T *> Singleton<T>::instance_(0);
template <class T>
boost::mutex Singleton<T>::mutex_;
|
参考资料:《C++ and the Perils of Double-Checked Locking》,Scott Meyers and Andrei Alexandrescu
更新
在C++11中,返回static对象是线程安全的(Dynamic Initialization and Destruction with Concurrency)。
实现线程安全的单例模式变得十分简单:
1
2
3
4
5
|
template <class T>
static T& Singleton::instance(){
static T instance;
return instance;
}
|