我最近读了一本关于系统软件的书。
其中有一个我不理解的例子。

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,这将导致返回错误的指针值。

10-06 11:50