线程安全的单间类

本文介绍了在线程安全环境下实现单例模式的一种方法,引用了来自Google的源码进行讲解。
摘要由CSDN通过智能技术生成

 在网上看到一篇关于将线程安全的单例实现,http://www.haogongju.net/art/1688900。下面是他提到的出自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_,不会引起数据不一致性问题。

这个模板类在一定程度上确实可以起到线程安全的作用,但有一个问题,多个线程new出多个实例,但只有一个被作为单例使用,那么其他的实例呢?没用上也没释放。此外,GetInstance中的while循环我也没明白是什么意思。

下面是从网上看到的一段代码,经过改造,我认为实现了支持多线程调用的单件


PVOID g_pwidCached = NULL;
class Singlton
{
public:
    ~Singlton(){};
    static Singlton* GetInstance()
    {
        Singlton *pwid = (Singlton*)g_pwidCached;
        if (!pwid)
        {
            pwid = new(nothrow) Singlton();
            if (pwid)
            {
                Singlton* pwidOld = reinterpret_cast<T*>(InterlockedCompareExchangePointerRelease(&reinterpret_cast<PVOID&>(g_pwidCached), pwid, NULL));
                if (pwidOld)
                {
                    delete pwid;
                    pwid = pwidOld;
                }
            }
        }
        return pwid;
    }
};
解释:定义一个全局变量g_pwidCached。线程中调用GetInstance()时首选获取g_pwidCached,并赋值给局部变量pwid,如果pwid不为空,则单例已经创建,直接返回。如果pwid为空,说明单例尚未创建,调用pwid = new Singlton()创建一个实例,注意,这里如果多线程并发调用,是有可能创建多个Singlton的实例的。
Singlton* pwidOld = reinterpret_cast<T*>(InterlockedCompareExchangePointerRelease(&reinterpret_cast<PVOID&>(g_pwidCached), pwid, NULL));
这里调用了原子操作函数IngerlockedCompareExchangePointerRelease,判断g_pwidCached是否为空,如果不为空,则执行g_pwidCached = pwid,并返回g_pwidCached原先的值给pwidOld。由于是原子操作,当多线程new出多个Singlton实例时,第一个执行 IngerlockedCompareExchangePointerRelease的线程会将它new出的Singlton实例赋给g_pwidCached,并返回null给pwidOld,其他线程阻塞。当其他线程解除阻塞执行到IngerlockedCompareExchangePointerRelease时,由于g_pwidCached已经不为null,则不执行g_pwidCached = pwid,并返回非null给pwidOld。这样就保证了多线程同时第一次调用GetInstance()时只有一个实例被赋给g_pwidCached,并且函数GetInstance()返回正确的pwid。函数中的
if (pwidOld)
{
      delete pwid;
      pwid = pwidOld;
 }
保证了多个线程new出的多个Singlton实例,除了被用上的那个实例外的其他实例都被delete掉。避免泄露。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值