美文网首页
理解Double-Checked Locking

理解Double-Checked Locking

作者: whosemario | 来源:发表于2017-05-05 09:35 被阅读0次

    传统的单例模式实现

    class Singleton {
    public:
        static Singleton* instance();
        ...
    private:
        static Singleton* pInstance;
    };
    
    Singleton* Singleton::pInstance = 0;
    
    Singleton* Singleton::instance() {
        if(pInstance == 0) {
            pInstance = new Singleton();
        }
        return pInstance;
    }
    

    在多线程环境下,这种写法会引起condition race。

    多线程基本实现

    class Singleton {
    public:
        static Singleton* instance();
        ...
    private:
        static Singleton* pInstance;
    };
    
    Singleton* Singleton::pInstance = 0;
    
    Singleton* Singleton::instance() {
        Lock lock;      // 获取锁
        if(pInstance == 0) {
            pInstance = new Singleton();
        }
        return pInstance;
    }   // 释放锁
    

    上面这种写法可以解决多线程下的condition race的问题,但性能消耗也会很大

    多线程double-check

    class Singleton {
    public:
        static Singleton* instance();
        ...
    private:
        static Singleton* pInstance;
    };
    
    Singleton* Singleton::pInstance = 0;
    
    Singleton* Singleton::instance() {
        if(pInstance == 0) {
            Lock lock;      // 获取锁
            if(pInstance == 0) {
                pInstance = new Singleton();
            }
        }   // 释放锁
        return pInstance;
    } 
    

    这种实现看似可以但问题很多。

    pInstance = new Singleton()
    

    一个Singleton的实例化其实包含三步

    1. 为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
                new (pInstance) Singleton;          // Step 2
            }
        }   // 释放锁
        return pInstance;
    } 
    

    最关键的是步骤2和3的顺序是未定义的!也就是说,pInstance可能先拿到一个没有调用构造函数的地址,此时另一个线程发现pInstance已经非空了,此时pInstance指向一个脏数据。

    即使步骤2和3在编译优化后保证了顺序,对于多Process架构来说,内存同步,也会导致问题。因为在Process A上步骤2和3是有序的修改内存,但是在Process B上发现两个地方的内存修改顺序可能是相反的。

    为了解决这个问题,我们使用内存屏障:

    Singleton* Singleton::getInstance() { 
        Singleton* tmp = m_instance; 
        ...         // insert memory barrier 
        if (tmp == NULL) { 
            Lock lock; 
            tmp = m_instance; 
            if (tmp == NULL) { 
                tmp = new Singleton; 
                ...     // insert memory barrier 
                m_instance = tmp; 
            } 
        } 
        return tmp; 
    }
    

    C++11前--pthread_once

    C++11前比较通用的方式是使用pthread_once来实现单例

    class Singleton {
    public:
        static Singleton* instance();
        static void init() {
            pIntance = new Singleton;
        }
        ...
    private:
        static Singleton* pInstance;
        static pthread_once_t ponce_;
    };
    
    Singleton* Singleton::pInstance = 0;
    pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;
    
    Singleton* Singleton::instance() {
        pthread_once(ponce_, &Singleton::init);
        return pInstance;
    } 
    

    可以看一下pthread_once的实现

    static int once_lock = LLL_LOCK_INITIALIZER;
    
    
    int
    __pthread_once (once_control, init_routine)
         pthread_once_t *once_control;
         void (*init_routine) (void);
    {
      /* XXX Depending on whether the LOCK_IN_ONCE_T is defined use a
         global lock variable or one which is part of the pthread_once_t
         object.  */
      if (*once_control == PTHREAD_ONCE_INIT)
        {
          lll_lock (once_lock, LLL_PRIVATE);
    
          /* XXX This implementation is not complete.  It doesn't take
         cancelation and fork into account.  */
          if (*once_control == PTHREAD_ONCE_INIT)
        {
          init_routine ();
    
          *once_control = !PTHREAD_ONCE_INIT;
        }
    
          lll_unlock (once_lock, LLL_PRIVATE);
        }
    
      return 0;
    }
    strong_alias (__pthread_once, pthread_once)
    hidden_def (__pthread_once)
    

    看起来非常像double checked的逻辑,但是lll_lock加入了内存屏障。

    C++11

    基于C++11实现单例就更加简单了

    class Singleton {
    public:
        static Singleton* instance() {
            static Singleton inst;
            return &inst;
        }
        ...
    };
    

    References

    1. C++ and Perils of Double-Checked Locking - http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
    2. 浅析单例模式与线程安全(Linux环境c++版本) - http://blog.csdn.net/cgxrit/article/details/43741771

    相关文章

      网友评论

          本文标题:理解Double-Checked Locking

          本文链接:https://www.haomeiwen.com/subject/vkyjtxtx.html