{
private:
Singleton() {};
Singleton( const Singleton & ); // no implement
Singleton & operator = ( const Singleton & ); // no implement
public:
static Singleton & getInstance()
{
if (NULL == m_pinstance)
{
m_pinstance = new Singleton();
}
return *m_pinstance;
}
private:
static Singleton *m_pinstance;
};
int main()
{
Singleton& singleton = Singleton::getInstance();
}
改正一下:
class Singleton
{
private:
Singleton();
Singleton( const Singleton & ); // no implement
Singleton & operator = ( const Singleton & ); // no implement
public:
static Singleton & getInstance()
{
if (NULL == m_pinstance)
{
m_pinstance = new Singleton();
}
return *m_pinstance;
}
private:
static Singleton *m_pinstance;
};
// Singleton.cpp
#include" Singleton.h"
Singleton * Singleton::m_pinstance;
Singleton::Singleton()
{}
//test.h
#include" Singleton.h"
int main()
{
Singleton& singleton = Singleton::getInstance();
}
问题: 申请的内存什么时候释放?
{
private:
Singleton(){}
Singleton( const Singleton & ); // no implement
Singleton & operator = ( const Singleton & ); // no implement
~Singleton() { //release resource }
public:
static Singleton & getInstance()
{
static Singleton singleton;
return singleton;
}
};
Singleton::Singleton()
{
cout<<"Singleton::Singleton()"<<endl;
}
int main()
{
Singleton& singleton = Singleton::getInstance();
}
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton * Instance()
{
if( 0== _instance.get())
{
_instance.reset( new Singleton);
}
return _instance.get();
}
protected:
Singleton(void)
{
cout <<"Create Singleton"<<endl;
}
virtual ~Singleton(void)
{
cout << "Destroy Singleton"<<endl;
}
friend class auto_ptr<Singleton>;
static auto_ptr<Singleton> _instance;
};
//Singleton.cpp
auto_ptr<Singleton> Singleton::_instance;
问题: 线程安全吗?
线程安全的C++ singleton模式
- 最简单的singleton
2 class Singleton {
3 public:
4 static Singleton* instance();
5 ...
6 private:
7 static Singleton* pInstance;
8 };
9
10 // from the implementation file
11 Singleton* Singleton::pInstance = 0;
12
13 Singleton* Singleton::instance() {
14 if (pInstance == 0) {
15 pInstance = new Singleton;
16 }
17 return pInstance;
18 }
【如果将指针换为静态对象也可以,就避免上面所讨论的内存泄露】
- 加锁保护的singleton
2 Lock lock; // acquire lock (params omitted for simplicity)
3 if (pInstance == 0) {
4 pInstance = new Singleton;
5 }
6 return pInstance;
7 } // release lock (via Lock destructor)
这样是绝对的安全了,但是由于加锁解锁的代价大,而instance又是可能被频繁调用的函数所以大大影响性能。事实上只要pInstance == 0的时候才可能出现问题,需要加锁,那么有了下面的写法:代码
2 if (pInstance == 0) {
3 Lock lock; // acquire lock (params omitted for simplicity)
4 pInstance = new Singleton;
5 }
6 return pInstance;
7 } // release lock (via Lock destructor)
但是这样是错误的,因为两个线程如果同时在2判断为true,虽然会在3处互斥,但是还是会轮流进入保护区,生成两个Singleton.于是有人想到下面的方法。
- DCL方法(Double Checked Locking)
代码
2 if (pInstance == 0) { // 1st test
3 Lock lock;
4 if (pInstance == 0) { // 2nd test
5 pInstance = new Singleton;
6 }
7 }
8 return pInstance;
9 }
这看上去很完美但是它也是有问题的
1.new operator分配适当的内存;
2.在分配的内存上构造Singleton对象;
3.内存地址赋值给_instance。
但是当编译器优化后执行顺序可能如下:
1.new operator分配适当的内存;
2.内存地址赋值给_instance;
编译器优化后的代码看起来像下面这样:
代码
2 if (pInstance == 0) {
3 Lock lock;
4 if (pInstance == 0) {
5 pInstance = // Step 3
6 operator new(sizeof(Singleton)); // Step 1
7 new (pInstance) Singleton; // Step 2
8 }
9 }
10 return pInstance;
11 }
这样如果一个线程按照编译器优化的顺序执行到5,这时后pInstance就已经非0了,而实际上它所指向的内存上Singleton对象还没有 被构造,这个时候有可能另一个线程运行到2,发现pInstance不是0,NULL,于是return pInstance,假设它又用这个指向还未构造对象的指针调用pInstance->doSomeThing() .........:(
未了避免这种情况,于是有了下面的做法
代码
2 if (pInstance == 0) {
3 Lock lock;
4 if (pInstance == 0) {
5 Singleton* temp = new Singleton; // initialize to temp
6 pInstance = temp; // assign temp to pInstance
7 }
8 }
9 return pInstance;
10 }
企图利用一个临时变量,仅当Singleton对象构造完成后才把地址赋给pInstance,然而很不幸,编译器会把你的临时变量视为无用的东西从而优化掉。。。。
这里插一句为什么用pthread之类的库能够解决问题,保证顺序,因为它们是非语言本身的,往往 强迫编译器产生与之适应的代码,往往会调用系统调用,很多是用汇编实现的。
- 加入volatile
2 public:
3 static Singleton* instance();
4 ...
5 private:
6 static Singleton* volatile pInstance; // volatile added
7 int x;
8 Singleton() : x(5) {}
9 };
10
11 // from the implementation file
12 Singleton* Singleton::pInstance = 0;
13
14 Singleton* Singleton::instance() {
15 if (pInstance == 0) {
16 Lock lock;
17 if (pInstance == 0) {
18 Singleton* volatile temp = new Singleton; // volatile added
19 pInstance = temp;
20 }
21 }
22 return pInstance;
23 }
下面考虑编译器inline构造函数和之后的instance()函数的样子
代码
2 Lock lock;
3 if (pInstance == 0) {
4 Singleton* volatile temp =
5 static_cast<Singleton*>(operator new(sizeof(Singleton)));
6 temp->x = 5; // inlined Singleton constructor
7 pInstance = temp;
8 }
9 }
问题出来了,尽管temp被声明为volatile,但是*temp不是,这意味着temp->x也不是,这意味着编译器可能会把6,7句换位置,从而使得另一个线程得到一个指向x域并未被构造好的Sigleton对象!
解决办法是我们不仅仅声明pInstance为volatile也将*pInstance声明为volatile.如下:
2 public:
3 static volatile Singleton* volatile instance();
4 ...
5 private:
6 // one more volatile added
7 static Singleton* volatile pInstance;
8 };
9
10 // from the implementation file
11 volatile Singleton* volatile Singleton::pInstance = 0;
12 volatile Singleton* volatile Singleton::instance() {
13 if (pInstance == 0) {
14 Lock lock;
15 if (pInstance == 0) {
16 // one more volatile added
17 volatile Singleton* volatile temp = new volatile Singleton;
18 pInstance = temp;
19 }
20 return pInstance;
21 }
这里Lock不需要声明为volatile因为它来自其它的线程库入pthread会提供相应保证其前面的代码不会被编译器调到后面,它后面的代码也不会被调到前面,相当于栅栏效应。
也许到现在这个版本是很完美的了,但是仍然可能失败,有下面两个原因:
- the Standard’s constraints on observable behavior are only for an abstract machine defined by the Standard, and that abstract machine has no notion of multiple threads of execution. As a result, though the Standard prevents compilers from reordering reads and writes to volatile data within a thread, it imposes no constraints at all on such reorderings across threads. 也就是说还是上面volatile解释语义时提到的,volatile,只保证线程内部顺序,不保证线程之间的。
- 就像const变量直到其构造函数完成之后不是const的一样,volatile变量直到其构造函数和完成之前也不是volatile的。volatile Singleton* volatile temp = new volatile Singleton; 要生成的变量直到new volatile Singleton完成之后才是volatile的。这意味这temp->x在构造函数中不是volatile的从而还是可能产生上面的问题和pInstance = temp 被编译器调换顺序。这个问题是可以解决的,办法如下,对默认构造函数改写,将对x的初始化列表构造初始化改为在构造函数中显示赋值。
2 {
3 static_cast<volatile int&>(x) = 5; // note cast to volatile
4 }
编译器会生成类似下面的代码:
代码
2 {
3 if (pInstance == 0) {
4 Lock lock;
5 if (pInstance == 0) {
6 Singleton* volatile temp =
7 static_cast<Singleton*>(operator new(sizeof(Singleton)));
8 static_cast<volatile int&>(temp->x) = 5;
9 pInstance = temp;
10 }
11 }
12 }
这保证了8和9不会被调换顺序。
- 一个可行的解决方案
如果你的程序运行在多处理器机器上,会面临CACHE一致性问题,就是说每一个处理器会有自己的CACHE, 而所有的处理器有共享的MAIN CACHE,什么时候将自己的CAHE内容写到MAIN CACHE中,以及什么时候去从MAIN CACHE中读数据都是问题,编译器有时候可能会把多个写入MAIN CACHE的数据按照地址升序写入已达到最佳速度,从而调换代码执行顺序。一般而言解决CACHE一致性问题采用栅栏,barrier方法。
2 Singleton* tmp = pInstance;
3
4 ... // insert memory barrier
5
6 if (tmp == 0) {
7 Lock lock;
8 tmp = pInstance;
9 if (tmp == 0) {
10 tmp = new Singleton;
11
12 ... // insert memory barrier
13 pInstance = tmp;
14 }
15 }
16 return tmp;
17 }
事实上我们不需要完整的双向栅栏,上面一处栅栏只需要保证前面的代码不要提到栅栏后面去,下面的栅栏保证后面的代码不要挪到栅栏前面去。
但是注意栅栏是平台相关的,尤其是assembler. 如果你的平台支持栅栏那么这就是一个可行的解决方案。注意GCC4.4.2中的string对于ref count引用计数,就是采用原子操作加栅栏实现的。
- 线程安全的Singleton模式总结
Singleton *instance = Singleton::instance();
instance->doThing1();
instance->donThing2();
而不要每次都调用instance(), Singleton::instance()->doThings().
也可以先做实验看看直接用这种加锁的方案是否真的很大程度影响了效率值得你去改变它。
2. 另外我们上面演示的代码 都是采用了lazily-initialized Singleton,就是说只有用到的时候才会有Singleton对象产生,如果程序没有调用instance就不会有Singleton对象的产生。 这样固然比eager initialization好,但是真的那么有必要吗,如果我们采用 eager initialization在进入main之前产生Singleton对象,
而一般的多线程程序都是在进入main后再启动的其它线程,就是说进入 mian之前是单线程环境,于是就没有这么多问题了。。。。boost 就是这么干的。
"1) sington 在进入main函数前初始化.
2)第一次使用时, singlton已得到正确的初始化(包括在static code中情况). Written by gavinkwoe"
"由于create_object将在被调用(static object_type & instance())之前进行初始化,因此singleton_default对象的初始化被放到了main之前。非常巧妙的一个设计"
可以参考 游戏人生博客 Writen by Fox(yulefox.at.gmail.com) 设计模式(三)——Singleton
http://www.cppblog.com/Fox/archive/2009/09/22/96898.html http://www.yulefox.com/20081119/design-patterns-03.html/
2 struct singleton
3 {
4 private:
5 struct object_creator
6 {
7 object_creator() { singleton<T>::instance(); } // 创建实例
8 inline void do_nothing() const { }
9 };
10 static object_creator create_object;
11 singleton();
12 public:
13 typedef T object_type;
14 static object_type & instance()
15 {
16 static object_type obj;
17 create_object.do_nothing();// 需要create_object的初始化
18 return obj;
19 }
20 };
21 template <typename T> typename singleton<T>::object_creator singleton<T>::create_object;
22
这个设计用到了模板可复用,如Singleton<MySingletonClass>::instance().
3. 如果我们允许每个线程能有一个自己的Singleton对象呢,那样就既可以推迟Singleton对象生成,也不用考虑多线程问题。