我最近读了一本关于系统软件的书。
其中有一个我不理解的例子。
volatile T* pInst = 0;
T* GetInstance()
{
if (pInst == NULL)
{
lock();
if (pInst == NULL)
pInst = new T;
unlock();
}
return pInst;
}
作者为什么要两次检查
(pInst == NULL)
? 最佳答案
当两个线程尝试同时首次调用GetInstance()
时,两个线程都将在第一次检查时看到pInst == NULL
。一个线程将首先获得锁,从而允许它修改pInst
。
第二个线程将等待锁可用。当第一个线程释放锁时,第二个线程将获得它,现在第一个线程已经修改了pInst
的值,因此第二个线程无需创建新实例。
只有lock()
和unlock()
之间的第二次检查是安全的。无需先检查即可工作,但会变慢,因为每次对GetInstance()
的调用都会调用lock()
和unlock()
。第一次检查可避免不必要的lock()
调用。
volatile T* pInst = 0;
T* GetInstance()
{
if (pInst == NULL) // unsafe check to avoid unnecessary and maybe slow lock()
{
lock(); // after this, only one thread can access pInst
if (pInst == NULL) // check again because other thread may have modified it between first check and returning from lock()
pInst = new T;
unlock();
}
return pInst;
}
另请参见https://en.wikipedia.org/wiki/Double-checked_locking(从interjay的注释复制)。
注意:此实现要求对
volatile T* pInst
的读取和写入访问都是原子的。否则,第二线程可能会读取仅由第一线程写入的部分写入的值。对于现代处理器,访问指针值(不是所指向的数据)是一种原子操作,尽管并非所有架构都可以保证。如果对
pInst
的访问不是原子的,则第二个线程在获取锁之前检查pInst
时可能会读取部分写入的非NULL值,然后可能在第一个线程完成其操作之前执行return pInst
,这将导致返回错误的指针值。