• 线程安全的懒单例模版类


    在C++编程里,单例是比较常用的一个模式,因为单例用起来兼具C++面向对象性,C面向过程性的爽感,它还有两个很受欢迎的恶魔级特性,
    1)不需关心创建时机(因为在进入main函数之前已经初始化了)
    2)不需显式回收(因为退出main过程后自动回收全局对象)
     
    但程序里有过多的单例,会影响程序的启动速度且增加内存占用,所以一般会采用懒单例
    模式替代之,即在需要使用的时候创建它,如下所示:
     
    template<class T>
    class Singlton{
    public:
        static T& GetInstance(){
            if(m_pInstance == NULL){
                m_pInstance = new T;
            }
            return *m_pInstance;
        }
    private:
        static T * m_pInstance;
    };
    template<class T>
    T* Singlton<T>::m_pInstance = NULL;
     
    这个单例类虽然看起来简洁明了,非常优美(先忽略它会在构造或析构时会带来什么问题),
    但由于单例本质上是一个全局对象,采用上述单例,在多线程下是非常有问题的:
    1)当Thread1通过判断(红色部分)进入if语句块执行申请内存操作,由于new操作是非原子
         操作(非线程安全),且会切换到内核模式。
    2)当Thread2进入if判断时,Thread1的内存分配还未完成(m_pInstance没被赋值,仍然
         为NULL),Thread2也会执行申请内存操作。
    3)当两者完成内存申请操作后,都会去给m_pInstance赋值,这会导致有一方数据被冲掉,
         出现数据一致性问题。
     
    好了,现在看一下线程安全的懒单例类(出自google源码):
     
    template <typename Ty_>
    class LazySingleton {
    public:
        static Ty_& GetInstance(){
    1:    while(me_ == NULL || me_ == (void*)-1){
    2:        PVOID result = InterlockedCompareExchangePointer((PVOID*)&me_, -1,   NULL);
    3:          if(*(PVOID*)&me_ == -1){
    4:             Ty_* new_instance = new Ty_();
    5:             InterlockedCompareExchangePointer((PVOID*)&me_, (PVOID)new_instance, -1);
                }
            }
    6:        return *const_cast<Ty_*>(me_);
        }
    private:
        static volatile Ty_ *me_;
    };
    template <typename Ty_>
    volatile Ty_* LazySingleton<Ty_>::me_;
     
    首先介绍一下上述类中两个生僻用法:
    a)关键字volatile,这个网上很多说法,如果在内存某处读取一个变量到寄存器进行操作,
       如果变量有任何变化,都会立即反应到内存中,而不会驻留在缓存(cache)中。
    b)函数PVOID __cdecl InterlockedCompareExchangePointer(PVOID volatile *Destination,
                                                                                     PVOID Exchange,
                                                                                     PVOID Comparand)
      是windows提供的一个比较并交换指针值的原子操作函数,它表示如果destination的值与Comparand值相等,则把Exchange的值赋给destination,如果不相等则什么都不做,
      该函数提供完全的内存栅栏(barrier)来保证内存操作有序。
     
    分析该类的线程安全性:
    1) 如果对象已经创建,则不会进入while语句,直接返回对象引用,这种情况不存在线程安全性,接下来分析对象未创建情况。
    2) 对象未创建(即me_== NULL),Thread1进入while循环,它首先通过第2行原子操作,如果me_==NULL,则me_=-1,否则什么都不做,即me_如果等于-1或其他值,直接进入
        第3行,如果有多个线程都进入第2行对me_进行写操作不会出现多线程问题(此处是为了防止与第5步产生同步问题),如果Thread1执行完第2行,进入第3行之前,Thread2已经给me_
        赋值,则会直接返回me_对象引用。
    3) 我们主要分析下第4行,第5行代码,Thread1进入第4行,执行new操作,如果此时没有其他线程并行,则会顺利进入第5行,交换后me_为new_instance地址而不再为-1,这样其
        它线程不再进入第3步,对象创建完成。如果有进程并行进入第4行,两个进程都创建了一个实例对象,但执行到第5行时,需要按序执行,最终只会有一个实例对象被赋值到me_,不会
        引起数据不一致性问题。
     
    结束语:在日常开发中一般不会有人写这么精细的懒单例类,因为即使是线程不安全类出问题的概率也很低,但这个类反应出多线程情况下一些复杂的问题需要我们关注。
  • 相关阅读:
    ASP.NET(C#)图片加文字、图片水印
    CMake构建Visual Studio中MFC项目的Unicode问题
    用Visual Studio 2008(VS)编译WebKit的r63513
    此时学习中
    ASP.NET进阶——初学者的提高(长期)
    继续努力
    程序员阿士顿的故事
    iOS 深拷贝和浅拷贝
    Javascript中this的取值
    Lisp的本质(The Nature of Lisp)
  • 原文地址:https://www.cnblogs.com/changping/p/2733427.html
Copyright © 2020-2023  润新知