注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

(转载)双重检查锁DCLP不是线程安全的?  

2014-10-08 12:01:07|  分类: UML+设计模式 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

(转载)双重检查锁DCLP不是线程安全的?

hanlray@gmail.com

Revision: 1.0 Date: 2005/09/29

http://blog.csdn.net/hansoft/article/details/493502

在做单实例的总结,把双检查锁不安全的原因举一下,当然,有N多的博客和论文,大家可以自行搜索一下,不过觉得这个说得更简单明了,转载一下:

DCLP(Double-Checked Loking Pattern)应该是一种优化模式,用于解决在多线程环境下,为了实现共享资源的lazy initialization,采用通常的锁机制带来的效率问题。其最常见的应用就是用来实现Singleton设计模式,ACE中的Singleton就是这么实现的。它怎么可能不是线程安全的呢?读了Scott Meyers Andrei Alexandrescu"C++ and the Perils of Double-Checked Locking",恐怕你就再也不敢使用DCLP了。本文是对该文的阐述。

 

DCLP实现Singleton的代码一般类似于:

 

        1  // from the header file

        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 }

这里pInstance = new Singleton;语句会引发三件事情:

 

Step 1: 分配内存来容纳一个Singleton对象

 

Step 2: 在分配好的内存中构造一个Singleton对象

 

Step 3: 使pInstance指向分配好的内存

 

如果编译器为该语句产生成的代码是是按照这个顺序,则不会出任何问题;但是编译器可能会颠倒第二步和第三步,生成类似下面的代码:

 

        Singleton* Singleton::instance() {

            if (pInstance == 0) {

                Lock lock;

                if (pInstance == 0) {

                    pInstance = // Step 3

                    operator new(sizeof(Singleton)); // Step 1

                    new (pInstance) Singleton; // Step 2

                }

            }

            return pInstance;

        }

这样的代码在多线程下就会有问题:假如线程A执行完Step 1step 3后由于线程调度而被挂起,线程B就会得到一个未初始化过的Singleton对象,这显然是错误的。那么为什么编译器可能会生成这种“错误的”代码呢?编译器的理由:第一,这种代码的行为是一种标准定义的抽象机的observable behavior,亦即这种代码被抽象机认为是正确的。第二,这种代码可能具有更高的效率。 然而问题就出在第一点上:C++的抽象机是单线程的!它根本不知道会有多线程执行的情况!这样的代码在单线程的情况下显然没有问题,因而被抽象机认为是正确的;但这样的代码却不是线程安全的。对C++/C的这种基础问题我们当然无能为力,但如果我们能够对指令的顺序进行约束,DCLP仍然可以是正确的,可是C++/C语言并没有给我们这样的办法;这也是为什么线程包(如Posix Threads)会有部分是用汇编这种移植性不好的语言写的,它们需要用汇编语言来保证一些操作的顺序性,从而使它们在多线程环境下能够正常工作。对此,Peter A. Buhr甚至提出了Are Safe Concurrency Libraries Possible?,针对采用库来为C++加入多线程支持提出了质疑。关于使线程成为C++语言的一部分的提议,看这里。

 

也许最终C++会把线程会引入到语言中,就像Java.Net CLI所做的那样;但是现在呢?DCLP当然是不能用了,而不用DCLP实现的Singleton,会大大增加锁的开销,因为每次instance函数调用的时候都会带来锁的操作。不过我们可以通过缓存Singleton对象指针来降低这个开销,不写:

 

            Singleton::instance()->transmogrify();

            Singleton::instance()->metamorphose();

            Singleton::instance()->transmute();           

       

而写:

 

            Singleton* const instance = Singleton::instance(); // cache instance pointer

            instance->transmogrify();

            instance->metamorphose();

            instance->transmute();           

       

甚至可以把instace指针存储到线程局部存储中,这样每个线程只会带来一次锁的开销。细想一下,DCLP的这个问题应该是由lazy initialization带来的,lazy initialization真的那么有用吗?eager initialization如何?即在程序开始运行时就初始化好资源。由于程序一开始都是单线程的,所以根本不用考虑同步问题,运行效率当然也最高。

 

  评论这张
 
阅读(500)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017