Singleton之C++部分一

采用静态或者全局变量的实现方案

http://blog.csdn.net/sheismylife/article/details/6429946

由于C++不能保证静态或者全局对象的构造函数的调用顺序以及析构顺序。所以如果程序中有多个用此方法实现的Singleton类,它们之间又有某种构造依赖关系和析构依赖关系,就会造成灾难性的后果。所以,只有当肯定不会有构造和析构依赖关系的情况下,这种实现才是合适的。

>优点实现简单,多线程下安全
>缺点如果有多个Singleton对象的创建顺序有依赖时,千万别用;不是lazy loading,有些浪费。

Meyers Singleton来控制构造顺序,但是不能控制析构顺序

Scott Meyer在<<Effective C++>>3rd Item4中提出了一个解决方案,当将non-local static变量移动到静态方法中成为local static变量的时候。C++保证当第一次静态方法被调用的时候,才会创建该静态变量。但是这里有一个疑问,创建顺序能够被控制了,可是析构顺序呢?我们只知道进程结束的时候,local static 变量会被析构,而且按照创建顺序的相反顺序进行。如果几个Singleton类的析构函数之间也有依赖关系,并且这种依赖顺序关系和LIFO顺序冲突,就会造成dead-reference问题。

>优点实现简单;用的时候才创建,比较节省。
>缺点多线程下不安全;如果有多个Singleton对象的析构顺序有依赖时,要小心

DCLP 98标准下是不可靠的,0x标准下是可靠的

DCLP 就是 Double-checked locking pattern.用于在多线程环境下保证只创建Singleton对象。第一次check不用加锁,但是第二次check和创建对象必须加锁。还要注意编译器可能会优化代码,导致DCLP模式失效。因此要使用volatile 修饰T* pInstance变量。先看一下 DCLP的实现代码:

  1.   
  2. class Singleton {  
  3. public:  
  4.     static Singleton* instance() {  
  5.         if (pInstance == 0) {  
  6.             Lock lock;  
  7.             if (pInstance == 0) {  
  8.                 pInstance = new Singleton;  
  9.             }  
  10.         }  
  11.         return pInstance;  
  12.     }  
  13. private:  
  14.     static Singleton * volatile pInstance;  
  15.     Singleton(){  
  16.     }  
  17. };  


 

在c++98标准下,这是不可靠的。原因有三点: 一,执行顺序得不到保证。编译器会优化代码,从而改变执行顺序。         pInstance = new Singleton; 这个语句会分成三步完成:1.分配内存,2.在已经分配的内存上调用构造函数创建对象,3.将对象赋值给指针pInstance.但是这个顺序很可能会被改变为1,3,2。如果A线程在1,3执行完后,B线程执行第一个条件判断if(pInstance ==0),此时锁不能起到保护作用。B线程会认为pInstance已经指向有效对象,可以去使用了。嘿嘿,灾难发生。主要原因是C++98标准中没有包含多线程,只假定是单线程,编译器的优化行为无视多线程环境,因此产生的优化代码可能会被去掉或者改变顺序。我们没有办法在98标准的采用标准c++语言来解决这个问题,只能采用平台相关的多线程API和与之兼容的编译器来解决。因此,从本质上来说,基于98标准,此问题无解。 二,volatile对于执行顺序也没有帮助。 三,多处理器的系统,B处理器看到变量值的顺序可能和A处理器写变量值的顺序不一致。 详细解释请参考Scott Meyers and Andrei Alexandrescu的论文:http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 技术总是发展的,2011标准的出台给DCLP带来了曙光,真的还能用么,拭目以待吧. 让我们先下个结论:

>缺点98标准下不可用
>优点让我们接受教育,包括Andrei和Scott meyer都犯过错。

Andrei说4.11 scott meyer阐述了0x标准下,由于有了线程概念,内存模型,sequence point被sequenced before 和 happens before取代, 有了atomic等等,DCLP又可以复活了。 http://cppandbeyond.com/2011/04/11/session-announcement-the-c0x-memory-model-and-why-you-care/ 具体谈了什么呢?我还没有找到相关的文档。

简单锁

于是又回到老土的锁方案,其实注意一下调用,还是能够提高效率的。

  1.   
  2. class Singleton {  
  3. public:  
  4.     static Singleton* instance() {  
  5.         Lock lock;  
  6.         if (pInstance == 0) {  
  7.             pInstance = new Singleton;  
  8.         }  
  9.         return pInstance;  
  10.     }  
  11. private:  
  12.     static Singleton * volatile pInstance;  
  13.     Singleton(){  
  14.     }  
  15. };  

 

客户调用时,在每个线程的开头都获得Singleton* p = Singleton::instance();以后就一直使用这个p变量,应该说还是能有效的降低同步的机会。避免频繁调用Singleton::instance()->就好。

>优点实现简单,线程安全
>缺点客户需要意识到,并且遵守少调用的原则。

 

程序开始之前创建Singleton对象

严格来说,这是个策略。将要初始化的放到程序最开始初始化。在这个策略下,以上几种方案都是可以的,包括DCLP。因为总是在单线程中创建对象。

查看评论
30楼 bolink5 2011-08-04 15:40发表 [回复] [引用] [举报]
29楼 kftbg 2011-06-11 17:21发表 [回复] [引用] [举报]
28楼 yao050421103 2011-06-01 09:16发表 [回复] [引用] [举报]
先顶一个,还没看到过把单例模式写的这么深入的文章。
个人认为如果使用单例模式,那么各个对象之间最好彼此正交。如果有依赖,首先应检查对象关系的设计是否合理;此外可以考虑利用参数传递替代构建时的依赖,也就是把对象构建时的依赖延迟到构建后的调用。这样基本可以解决楼主提到的问题。
27楼 Matrix_Designer 2011-05-30 14:23发表 [回复] [引用] [举报]
if (pInstance == 0) {
Lock lock;
if (pInstance == 0) {
Singleton tmp = new Singleton;
pInstance = tmp;
}
}
26楼 xiongzaibinggan 2011-05-27 15:08发表 [回复] [引用] [举报]
看了这篇文章我更加体会到“CPP好用难学”这句话的含义了。路漫漫其修远兮,吾将上下而求索......
Re: sheismylife 2011-05-27 19:09发表 [回复] [引用] [举报]
回复 xiongzaibinggan:长征路上有很多同行者
25楼 sheismylife 2011-05-26 12:55发表 [回复] [引用] [举报]
新标准还没有最终版本问世,需要等半年。估计是程序中显式采用atomic模板来告诉编译器产生的代码(其实是指令)应该在多线程环境下遵守内存一致性的要求。至于CPU的乱序问题,交给编译器这一层去处理了。计算机领域的问题,都可通过加一层来解决。而我们已经有了编译器这一层。
24楼 tossense 2011-05-26 12:31发表 [回复] [引用] [举报]
新标准引入的这些概念只是用来约束编译器的,应该约束不了cpu吧?还是编译器编写者会根据新标准的约束要求去根据不同的target platform加入相应的platform相关的约束指令?
23楼 tossense 2011-05-26 12:30发表 [回复] [引用] [举报]
多谢lz简洁明了的分析。有一个问题请教,即使编译器不将new的顺序优化成1--3--2,仍然维持1--2--3,是不是cpu认为2和3不存在相关性乱序执行也会引起问题?
这样的话即使编译器不激进也还是解决不了问题?
Re: sheismylife 2011-05-26 12:51发表 [回复] [引用] [举报]
回复 tossense:对于多个CPU的情况,我并不熟悉。但是根据Andrei和Meyer的论文,的确会有问题。说到底是CPU的执行指令要符合程序员的预期,而0x98下的确不能保证这点。
22楼 ElaxChin 2011-05-25 22:44发表 [回复] [引用] [举报]
博主的文章不错,之前我使用singleton时,在多线程程序也犯过文章叙述的错误,当时查了很久才查出来。
21楼 mybestkoo1 2011-05-24 13:48发表 [回复] [引用] [举报]
void* p = malloc( sizeof(Singleton) );
pInstance = (Singleton*)(new(p)Singleton());
这样如何?
Re: sheismylife 2011-05-24 14:37发表 [回复] [引用] [举报]
回复 mybestkoo1:不行,还是临时变量的方法。至于先分配内存,后调用构造函数的做法,就是默认new Singleton()的内部实现,你只是自己把代码显式的写出来了而已。
20楼 AthrunArthur 2011-05-23 18:43发表 [回复] [引用] [举报]
用pthread_once怎么样呢?
Re: sheismylife 2011-05-24 21:40发表 [回复] [引用] [举报]
回复 AthrunArthur:估计是可以的,不过用起来比较复杂。
19楼 qin40704 2011-05-23 16:29发表 [回复] [引用] [举报]
18楼 pbe_sedm 2011-05-23 12:26发表 [回复] [引用] [举报]
有一个非常优秀的库 loki,里面就有 Singleton 的实现,大师写的,既可用也可拿来研究,与该库对应的书籍是《c++设计新思维》
Re: sheismylife 2011-05-23 13:51发表 [回复] [引用] [举报]
回复 pbe_sedm:我说的Andrei也犯了措,基本就是指他在2001年的Modern c++ design中的Loki::SingletonHolder内部采用了DCLP。他后来2004年的论文中实际上已经推翻了他的Loki::SingletonHolder的实现。
Re: pbe_sedm 2011-05-24 08:51发表 [回复] [引用] [举报]
回复 sheismylife:原来如此,受教了!
Re: sheismylife 2011-05-23 13:46发表 [回复] [引用] [举报]
回复 pbe_sedm:Loki的库就是后面要谈到的,有问题。C++ Moder design和Loki的命运会有很大变化,因为0x标准直接允许可变模板参数。
17楼 leiXure 2011-05-23 09:11发表 [回复] [引用] [举报]
改用创建函数返回实例呢?

Singleton*p = createSingleton();
pInstance = p;
Re: sheismylife 2011-05-23 13:47发表 [回复] [引用] [举报]
回复 leiXure:这就是应用开发程序员和编译器程序员的竞赛,谁都可能会赢,但是应用开发程序员却承受不起输的后果。
16楼 effort_man 2011-05-22 14:56发表 [回复] [引用] [举报]
15楼 Sun_mingtao 2011-05-21 14:56发表 [回复] [引用] [举报]
14楼 xhzhf 2011-05-21 10:30发表 [回复] [引用] [举报]
volatile没必要的,编译器没有那么傻得,他看到lock的地方,不会利用寄存器里的值,每次都会去实际内存的值

http://topic.csdn.net/u/20101219/16/4c6aaf4a-cedd-42e0-b593-2683cd3dda1b.html
Re: sheismylife 2011-05-21 10:55发表 [回复] [引用] [举报]
回复 xhzhf:这个结论有时候不敢有足够的勇气下。因为依赖于编译器,如果不想用volatile,建议把手头上的编译器手册仔细阅读后再用。并在代码上加上注释,如下:

//这里不用volatile的原因是我查过了vc编译器手册,版本n中可以不需要使用volatile
int x;
Re: xhzhf 2011-06-05 21:27发表 [回复] [引用] [举报]
回复 sheismylife:我可以肯定是不需要的。按照你的想法,那所有的用锁保护的变量,都必须声明成volatile, 但是你可以在很多开源源代码看到并不需要使用volatile。大师虽然会犯错,但是不会这么容易犯错
Re: sheismylife 2011-06-06 09:30发表 [回复] [引用] [举报]
回复 xhzhf:这个话题变成了在锁的里面volatile是否有必要。这个话题的确有很多争论。在没有找到权威答案之前,我是坚持使用volatile的,至少我也没有看出volatile对我的程序有什么伤害啊。而万一,你错了呢?
13楼 rugeqing 2011-05-20 21:09发表 [回复] [引用] [举报]
12楼 jibeiyudehaitun 2011-05-20 21:08发表 [回复] [引用] [举报]
学习了啊
11楼 fipl 2011-05-20 19:54发表 [回复] [引用] [举报]
up
程序员的自我修养 我复印了一本~
10楼 liying0515 2011-05-20 13:31发表 [回复] [引用] [举报]
9楼 masefee 2011-05-20 13:26发表 [回复] [引用] [举报]
这是我写的Singleton,欢迎交流:http://blog.csdn.net/masefee/archive/2010/09/23/5902266.aspx
Re: sheismylife 2011-05-20 13:38发表 [回复] [引用] [举报]
回复 masefee:
看了,写的很好。我的目标是0x标准下的DCLP,有任何消息请通知。
8楼 fanzhenyong 2011-05-20 13:22发表 [回复] [引用] [举报]
Singleton已经研究的很细了,建议作者参考文档:
Re: fanzhenyong 2011-05-20 13:29发表 [回复] [引用] [举报]
回复 fanzhenyong:刚才链接没有贴上 http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 时序问题可以通过一个临时变量或者一个子函数解决
Re: sheismylife 2011-05-20 13:25发表 [回复] [引用] [举报]
回复 fanzhenyong:参考什么文档?
Re: fanzhenyong 2011-05-20 13:34发表 [回复] [引用] [举报]
回复 sheismylife:没注意你已经参考了 我再找一篇
Re: fanzhenyong 2011-05-20 13:44发表 [回复] [引用] [举报]
回复 fanzhenyong:C++ in Theory: Why the Double Check Lock Pattern Isn`t 100% Thread Safe
Re: fanzhenyong 2011-05-20 13:45发表 [回复] [引用] [举报]
回复 fanzhenyong:http://www.devarticles.com/c/a/Cplusplus/C-plus-in-Theory-Why-the-Double-Check-Lock-Pattern-Isnt-100-ThreadSafe/
Re: sheismylife 2011-05-20 13:47发表 [回复] [引用] [举报]
回复 fanzhenyong:谢谢。
7楼 healer_kx 2011-05-20 12:30发表 [回复] [引用] [举报]
写得挺好,但是我最近学着尽量不用Singleton。
6楼 zyyoung 2011-05-20 09:13发表 [回复] [引用] [举报]
怎么看 都像是在说,lock的使用技巧
Re: sheismylife 2011-05-20 09:17发表 [回复] [引用] [举报]
回复 zyyoung:这不是我的本意,居然被你看出来了。:)
5楼 wgm001 2011-05-20 00:49发表 [回复] [引用] [举报]
单件模式没什么太多意义, 还不如全局指针, 专门在入口为这个指针初始化和创建对象...
Re: sheismylife 2011-05-20 09:18发表 [回复] [引用] [举报]
回复 wgm001:在不需要面向对象设计思想的地方,Singleton的确没有意义。
Re: wgm001 2011-05-20 09:51发表 [回复] [引用] [举报]
回复 sheismylife:Singleton貌似没什么面向对象设计思想吧?想听楼主主讲讲Singleton的面向对象设计思想……
Re: sheismylife 2011-05-20 10:24发表 [回复] [引用] [举报]
回复 wgm001:如果有个虚拟人生的系统,我的儿子是我的唯一的儿子。他就是Singleton对象。
4楼 sheismylife 2011-05-20 00:04发表 [回复] [引用] [举报]
加临时变量的方法是不可靠的,scott和andrei的论文提过了。主要原因是不要忘记编译器也是一帮整天折腾优化代码的聪明人写的,他们很容易发现你的企图,然后击败你。
:)
Re: linyt 2011-05-20 09:45发表 [回复] [引用] [举报]
回复 sheismylife:高手,看来如果想在C++98里面实现DCLP,只能通过平台相关的memory barrier来做了。
Re: sheismylife 2011-05-20 10:25发表 [回复] [引用] [举报]
回复 linyt:对了,你完全正确。
3楼 b2b160 2011-05-19 22:56发表 [回复] [引用] [举报]
if (pInstance == 0) {
Lock lock;
if (pInstance == 0) {
Singleton*p = new Singleton;
pInstance = p;
}
} return pInstance;

如果这样呢?
2楼 sheismylife 2011-05-19 22:36发表 [回复] [引用] [举报]
程序员的自我修养,多谢,你让我知道了一本好书。准备买一本看看。
1楼 liyongfa 2011-05-19 21:41发表 [回复] [引用] [举报]
《程序员的自我修养》里面也提到这一点了,很好
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值