西城

指尖代码,手上年华

联系 聚合 管理
  20 Posts :: 0 Stories :: 62 Comments :: 0 Trackbacks
在看POCO网络库的时候,其中实现了一个singleton模式,提到了DCLP的不可行性。就去查阅了一下,
找到了这篇文章。原文太长,将其意思大略整理如下。

singleton差不多是所有设计模式中最为常见的一个,但却不是线程安全的。
DCLP就是为了消除此缺点而设计出来的——Double Checked Locking Pattern。但却仍然是
不可靠的。


一般的singleton实现为:
// from the header file
class Singleton {
public:
static Singleton* instance();

private:
static Singleton* pInstance;
};
// from the implementation file
Singleton* Singleton::pInstance = 0;
Singleton* Singleton::instance() {
if (pInstance == 0) {                //Line 1
   pInstance=new Singleton;         //Line 2
}
return pInstance;
}
单线程模式下,这种方法工作的很好。但在多线程模式下却是有问题的。
假设线程A进入了instance函数,执行到Line 1,然后被挂起。当它被挂起的时候,它
刚测得pInstance是NULL,所以还没有Singleton对象被创造出来。
然后线程B进入instance并且执行到line1,发现pInstance是NULL,然后执行下一句,创建了一个
Singleton并且使pInstance指向它。然后返回pInstance.
当线程A继续执行的时候,它会执行Line2,创建一个Singleton并使pInstance指向它,这就
破坏了singleton的意义,因为创建了两个singleton.
要想使其是线程安全的,需要在测试pInstance之间加一个lock.
Singleton* Singleton::instance() {
Lock lock;
// acquire lock (params omitted for simplicity)
if (pInstance == 0) {
pInstance = new Singleton;
}
return pInstance;
}
// release lock 

这样有一个和很大的缺点——代价太高,每次访问都需要一个lock.但实际上,我们只需要在
第一次创建的时候加一个锁,而且应该是instance第一次被调用的时候。如果在运行时
instance被调用了n次,我们只需要在第一次调用的时候加锁就可以了。DCLP就是为了
解决这个问题而设计的--------------去掉那些不必要的LOCK。
Singleton* Singleton::instance() {
if (pInstance == 0) {
// 1st test
Lock lock;
if (pInstance == 0) {
// 2nd test
pInstance = new Singleton;
}
}
return pInstance;
}
DCLP在加锁之前先测试pInstance是否为空,尽在其为NULL时才会需要一个LOCK,第二次测试也是
必要的,因为有可能另一个线程在第一次测试pInstance和请求LOCK时执行了new.

问题之所在:
pInstance=new Singleton;分为以下三步:
1.分配内存(sizeof(Singleton).
2.在分配的内存上创建一个Singleton对象。
3.让pInstance指向这块内存。问题就在于编译器不一定按照顺序执行这三步。 有时候编译器会将
第二步和第三步互换。此时情况可能如以下所示:
Singleton* Singleton::instance() {
if (pInstance == 0) {
Lock lock;
if (pInstance == 0) {
pInstance =
// Step 3
operator new(sizeof(Singleton)); // Step 1
// Step 2
new (pInstance) Singleton;
}
}
return pInstance;
}
在实际的DCLP代码中,step2是可能抛出异常的,这时需要保证pInstance没有变化(setp3未
执行)。所以一般并不能把step3移到step2之前,但有时候是可以的,比如说step2并不抛出异
常。
现在如果线程A进入instance,进行第一次测试,请求了一个LOCK,然后执行了step1和step3.
然后被挂起。此时pInstance是NO-NULL,但是没有singleton对象被创建出来。
然后,线程B进入instance,发现pInstance非空,然后将其返回,然后解引用,但是却没有对象。
所以,DCLP只有在step1和step2在step3之前完成的情况下才能正常工作,但C/C++并不提供这样的
保证。
posted on 2012-04-20 20:20 西城 阅读(6667) 评论(7)  编辑 收藏 引用 所属分类: C/C++

Feedback

# re: 为什么DCLP是不可行的?(1) 2012-04-21 10:36 runner.mei
说了半天,无非是想法要为 pInstance 加上 violate 修饰符嘛, 最新的 vc++ 和 gcc 都扩展了 violate 的语义, 保证了它的原子性(Atomicity)和顺序性(Ordering)了。DCLP是可行的  回复  更多评论
  

# re: 为什么DCLP是不可行的?(1) 2012-04-21 10:39 runner.mei
说了半天,无非是想说要为 pInstance 加上 violate 修饰符嘛, 最新的 vc++ 和 gcc 都扩展了 violate 的语义, 保证了它的原子性(Atomicity)和顺序性(Ordering)了。DCLP是可行的。
已经不是原创了,还分成几篇,赚点击。  回复  更多评论
  

# re: 为什么DCLP是不可行的?(1) 2013-03-15 11:32 Eric.Tsai

pInstance = new Singleton;
改成
Singleton *pTemp = new Singleton;
pInstance = pTemp;
不就可以避免了吗? 还是我理解不够透彻?  回复  更多评论
  

# re: 为什么DCLP是不可行的?(1) 2013-03-15 11:44 Eric.Tsai
看了原文才知道忘记"编译器优化了"@Eric.Tsai
  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理