首页 > 解决方案 > 互斥锁指针双重NULL检查的原因是什么

问题描述

我最近读了一本关于系统软件的书。里面有一个例子我不明白。

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

为什么作者要检查(pInst == NULL)两次?

标签: c++if-statementlocking

解决方案


当两个线程GetInstance()同时第一次尝试调用时,两者都会pInst == NULL在第一次检查时看到。一个线程将首先获得锁,这允许它修改pInst.

第二个线程将等待锁可用。当第一个线程释放锁时,第二个会得到它,现在 的值pInst已经被第一个线程修改了,所以第二个不需要创建新实例。

只有lock()和之间的第二次检查unlock()是安全的。它可以在没有第一次检查的情况下工作,但它会更慢,因为每次调用GetInstance()都会调用lock()and 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不是原子的,则第二个线程可能会在获取锁之前进行检查时读取部分写入的非 NULL 值pInst,然后可能会return pInst在第一个线程完成其操作之前执行,这将导致返回错误的指针值。


推荐阅读