单件模式是非线程安全的:
// Single threaded version
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// other functions and members...
}
这段在使用多线程的情况下无法正常工作。在多个线程同时调用getHelper()时,必须要获取锁,否则,这些线程可能同时去创建对象,或者某个线程会得到一个未完全初始化的对象。
锁可以通过代价很高的同步来获得,就像下面的例子一样。
// Correct but possibly expensive multithreaded version
class Foo {
private Helper helper = null;
public synchronized Helper getHelper() {
if (helper == null) {
helper = new Helper();
}
return helper;
}
// other functions and members...
}
只有getHelper()的第一次调用需要同步创建对象,创建之后getHelper()只是简单的返回成员变量,而这里是无需同步的。 由于同步一个方法会降低100倍或更高的性能[2], 每次调用获取和释放锁的开销似乎是可以避免的:一旦初始化完成,获取和释放锁就显得很不必要。许多程序员一下面这种方式进行优化:
检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量。
获取锁
第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量。
否则,初始化并返回变量。
// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
}
return helper;
}
// other functions and members...
}
直觉上,这个算法看起来像是该问题的有效解决方案。然而,这一技术还有许多需要避免的细微问题。例如,考虑下面的事件序列:
线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
由于某些编程语言的语义,编译器生成的代码允许在线程A执行完变量的初始化之前,更新变量并将其指向部分初始化的对象。
线程B发现共享变量已经被初始化,并返回变量。由于线程B确信变量已被初始化,它没有获取锁。如果在A完成初始化之前共享变量对B可见(这是由于A没有完成初始化或者因为一些初始化的值还没有穿过B使用的内存(缓存一致性)),程序很可能会崩溃。
以上内容出自伟大的维基百科:http://zh.wikipedia.org/zh/%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5%E9%94%81%E5%AE%9A%E6%A8%A1%E5%BC%8F
使用pthread_once语义可以解决上述问题:The purpose of pthread_once(pthread_once_t *once_control, void (*init_routine) (void)) is to ensure that a piece of initialization code is executed at most once. The once_control argument points to a static or extern variable statically initialized to PTHREAD_ONCE_INIT.The first time pthread_once is called with a given once_control argument, it calls init_routine with no argument and changes the value of the once_control variable to record that initialization has been performed. Subsequent calls to pthread_once with the sameonce_control argument do nothing.
线程安全的例子:
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<boost/noncopyable.hpp>
using namespace std;
using namespace boost;
template<typename T>
class singleton:noncopyable{
public:
static T& instance(){
pthread_once(&ponce,&singleton::init);//第一次调用才会执行init,此后将改变ponce并将已经执行记录存入ponce
return *value;
}
private:
singleton();
~singleton();
static void init(){
value=new T();//这里只能调用T的默认构造函数,若要用户指定T的构造方式,可能需要模板特化...不怎么熟悉...汗
}
private:
static pthread_once_t ponce;
static T* value;
};
template<typename T>//静态成员类外初始化
pthread_once_t singleton<T>::ponce=PTHREAD_ONCE_INIT;//ponce初始化
template<typename T>
T* singleton<T>::value=NULL;
class test{//测试对象
public:
void show(){
cout<<"show()"<<endl;
}
};
int main(){
test& p=singleton<test>::instance();//注意使用方法
p.show();
test& q=singleton<test>::instance();
if(&p==&q)
cout<<"singleton success"<<endl;
else
cout<<"singleton failure"<<endl;
return 0;
}
程序输出:
show()
singleton success