[C++][线程安全]单例模式下双检查锁和线程

问题

在设计模式中,有一个很经典的模式-单例模式,它可能是实现上最简单的模式,在代码中也经常使用,在单线程下,毫无疑问延迟化加载是比较常用的,但是在多线程条件下,单例模式的延迟加载可能就会出现一些问题。

如以下的代码:

T* GetInstance()
{
	if (pInst == NULL)
	{
		pInst = new T;
	}
	return pInst;
}

如果检测代码和实例化代码不是同一线程,则很容易出现返回NULL的现象。

经典的单例模式下的双重检测

解决以上问题就是加并发锁,我们将需要实例化的对象加锁,于是有了以下代码:

T* GetInstance()
{
	if (pInst == NULL)
	{
		lock();
		if (pInst == NULL)
			pInst = new T;
		unlock();
 	}
 	return pInst;
}

为什么要用两层if检查,第一层的if检查是因为当实例为空的时候,才去对实例加锁,这样可以避免多次对lock资源的调用,当第二层if检测的时候,才是程序要对程序进行初始化。

乍看这种代码是没有问题的,但是问题的来源是CPU的乱序执行,C++的New操作实际上包含了两个步骤:

  1. 分配内存
  2. 调用构造函数

所以pInst = new T包含了三个步骤:

  1. 分配内存
  2. 在内存的位置上调用构造函数
  3. 将内存的地址赋值给pInst

因为(2)和(3)是可以颠倒的,所以可以出现这样的情况:pInst的值已经不是NULL,但对象仍然没有构造完毕。如果另外一个线程对GetInstance的调用,此时第一个if为false,这样就会返回一个未构造完成的对象,此时可能会导致程序崩溃。

解决思路

许多体系结构都提供barrier指令,POWERPC提供了其中一条名为lwsync的指令,我们可以这样来保证线程安全:

#define barrier() __asm__ volatile ("lwsyc")
volatile T* pInst = 0;
T* GetInstance()
{
	if (!pInst) 
	{
		lock();
		if (!pInst)
		{
			T* temp = new T;
			barrier()
			pInst = temp;
		}
		unlock();
	}
	return pInst;
}

由于barrier的存在,对象的构造一定会在barrier执行之前完成,所以这样不会出现一些问题。

你可能感兴趣的:(C++,c++/c基础,线程anquan)