通过惰性求值使 Meyers 的单例线程安全快速地

Make Meyers' Singleton thread safe and fast with lazy evaluation

本文关键字:线程 单例 安全 Meyers      更新时间:2023-10-16

所以我读了很多关于为什么这个实现不是线程安全的。但我没有找到答案,如何使它线程安全和快速?使其线程安全的变体是添加互斥(或者在某些情况下,只添加关键部分就足够了),但这会使该方法变得更慢。那么,有没有一种变体可以让这个代码线程安全快速,或者至少现在和添加互斥锁一样慢?

static Singleton& getInstance()
{
     static Singleton singleton;
     return singleton;
}

PS:是的,我也读了很多关于线程安全Singleton实现的文章。当我们使用Singleton指针作为类的成员时,问题是关于Singleton的这个特定实现,没有指针和new,并且使用惰性评估。

对于某些编译器,您所拥有的可能已经具有线程安全保证。如果你不关心代码的可移植性,并且它对你有效,那么就对它感到满意

如果您有可用的boost线程,您可以使用boost::call_once进行初始化。这是线程安全的,并且只在第一次初始化时花费。

当然,你也可以通过初始化"Meyers"单例创建,即在创建访问它的线程之前第一次访问它,使其完全线程安全。如果你已经实现了很多这样的单例,请考虑这样做。

所有这些,即使是boost::call_once也只适用于对象的创建。然而,如果由多个线程访问,它的访问可能需要单独的同步技术。

(顺便说一句,Meyers Effective C++的第47项提到了这个单例,这表明该标准的后续修订使其线程安全,并且后续编译器符合它,但它确实警告您,并非所有编译器都符合它)。

好吧,所以如果没有互斥,你根本无法做到这一点,但你可以快速生成互斥。

首先声明一个类来保存互斥锁,以及一个就绪标志(下面的InitMutex)。当调用GrabMutex(),而readyfalse时,实际的互斥被捕获。当调用ReleaseMutex()时,它根据GrabMutex()发送的标志做正确的事情。在第一次通过之后,ready变为true,因为静态对象现在已经初始化,所以不需要抓取互斥对象。

现在,声明一个类,该类在构造函数中调用GrabMutex(),在析构函数中调用ReleaseMutex(flag),并在本地保存标志(下面的InitMutexHolder)。

现在,在您的常规getSingleton函数中实例化该类。这将确保singleton初始化在第一次通过时被互斥,并且如果多个线程竞争,它们将在互斥上排队。但是,一旦初始化了singleton,ready就会变为true,访问就会很快。在执行return theSingleton之后,会神奇地调用析构函数,从而释放互斥对象(如果没有获取互斥对象,则不执行任何操作)。

但是,theSingleton()函数中的代码主体没有改变,我们只在每个单例中添加了一个控制对象,并在调用中添加了管理线程安全的堆栈对象。

关于写障碍的注意事项:由于每当ready为false时,代码都是安全的,并且ready在对象初始化之前不能为true,因此假设写操作立即可见,代码总体上是安全的。但是,在ready=true可见时可能存在延迟,因为在设置ready true之后不存在写入屏障。然而,由于ready=false是保守的、安全的情况,因此在延迟期间仍然保持安全。

class InitMutex
{
public:
   InitMutex() : ready(false) { }
   bool  GrabMutex()
   {
      if (!ready)
      {
         mutex.Grab();
         return true;
      }
      else
      {
         return false;
      }
   }
   void ReleaseMutex(bool flagFromGrabMutex)
   {
      if (flagFromGrabMutex)
      {
          mutex.Release();
          ready = true;
      }
   }
   Mutex   mutex;
   bool    ready;
};
class InitMutexHolder
{
public:
    InitMutexHolder(InitMutex & m)
    : initMutex(m)
    {
       inMutex = initMutex.GrabMutex();
    }
    ~InitMutexHolder()
    {
       initMutex.ReleaseMutex(inMutex);
    }
private:
    bool inMutex;
    InitMutex & initMutex;
};
static InitMutex singletonMutex;
static Singleton & getSingleton()
{
    InitMutexHolder mutexHolder(singletonMutex);
    {
       static Singleton theSingleton;
       return theSingleton;
    }   
}

我的问题的答案似乎最好地表达在Fred Larson的评论中:

"快速,线程安全,懒惰-选择任意两个。"