简体   繁体   English

懒惰评估使Meyers的Singleton线程安全快速

[英]Make Meyers' Singleton thread safe and fast with lazy evaluation

So i read a lot about why this implementation isn't thread-safe. 所以我读了很多关于为什么这个实现不是线程安全的。 But I didn't find the answer how to make it thread safe and fast? 但是我没有找到答案,如何使它安全快速地线程化? The variant of make it thread safe is to add mutex(or in some cases just critical section will be enough), but it will make this method much more slower. 使线程安全的变体是添加互斥锁(或者在某些情况下,仅关键部分就足够了),但这会使此方法更慢。 So is there a variant to make this code thread safe and fast, or at least now as slow as adding mutex there? 那么,有没有变种可以使此代码线程安全,快速,或者至少现在与在其中添加互斥锁一样慢?

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

PS: and yes, I also read a lot about thread safe Singletom implementation when we use Singleton pointer as a member of the class, the question is about this particular implementation of Singleton, without pointers and new and using lazy evaluation. PS:是的,当我们使用Singleton指针作为类的成员时,我还阅读了很多有关线程安全的Singletom实现的信息,问题是关于Singleton的这种特殊实现,没有指针,没有指针,并且使用了惰性求值。

For some compilers what you have may already have a threadsafe guarantee. 对于某些编译器,您可能已经拥有线程安全保证。 If you don't care about code portability and it works for you, then be happy with it. 如果您不关心代码可移植性并且它对您有用,那么请对此感到满意。

If you have boost thread available you can use boost::call_once to initialise. 如果您有可用的加速线程,则可以使用boost::call_once进行初始化。 This is threadsafe and only costs on first initialisation. 这是线程安全的,并且仅在首次初始化时花费。

You can of course also make a "Meyers" singleton creation totally thread-safe by initialising it, ie accessing it the first time, before you create threads that access it. 当然,您也可以通过创建“ Meyers”单例创建完全线程安全,方法是对其进行初始化,即在创建访问它的线程之前首次对其进行访问。 If you have lots of these singletons already implemented consider doing that. 如果您已经实现了很多这样的单例,请考虑这样做。

All of these, even boost::call_once only applies to the creation of the object. 所有这些,甚至boost::call_once仅适用于对象的创建。 Its access, however, may need separate synchronisation techniques if accessed by multiple threads. 但是,如果被多个线程访问,则可能需要单独的同步技术。

(Incidentally Item 47 of Meyers Effective C++ where this singleton is mentioned suggests that later revisions of the standard made it thread-safe and later compilers conformed with it, however it does warn you that not all compilers are compliant yet). (顺便提一句,提到此单例的Meyers Effective C ++项目47表示该标准的更高版本使其具有线程安全性,后来的编译器也对此予以遵守,但是它确实警告您并非所有编译器都兼容)。

OK, so you can't do it without a mutex at all, but you can make that mutex fast. 好的,因此没有互斥量是无法做到的,但是可以使该互斥量更快。

First declare a class to hold the mutex, and a ready flag ( InitMutex below). 首先声明一个包含互斥量的类,以及一个就绪标志(下面的InitMutex )。 When GrabMutex() is called and ready is false , the actual mutex is grabbed. GrabMutex()并且readyfalse ,将GrabMutex()实际的互斥量。 When ReleaseMutex() is called it does the right thing based on the flag sent from GrabMutex() . 调用ReleaseMutex() ,它将根据GrabMutex()发送的标志执行正确的操作。 After the first time through, ready goes true as the static object is now initialized, so the mutex doesn't need to be grabbed. 第一次完成后,由于静态对象现在已初始化,因此就绪变为真,因此不需要获取互斥体。

Now, declare a class which calls GrabMutex() in the constructor and ReleaseMutex(flag) in the destructor, saving the flag locally ( InitMutexHolder below). 现在,声明一个在构造函数中调用GrabMutex()并在析构函数中调用ReleaseMutex(flag)的类,将该标志保存在本地(下面的InitMutexHolder )。

Now, instantiate that class inside your regular getSingleton function. 现在,在常规的getSingleton函数中实例化该类。 This will ensure that the singleton initialization is mutexed the first time through, and if multiple threads contend they will line up on the mutex. 这将确保单例初始化在第一次通过时被互斥,并且如果有多个线程争用,它们将在互斥体上对齐。 But once the singleton is initialized, ready goes true, and access will be fast. 但是一旦初始化了单例, ready就可以了,访问将很快。 The destructor is called magically after return theSingleton is executed, which releases the mutex (or does nothing if the mutex was not taken). return theSingleton执行后,析构函数将被魔术调用,这将释放互斥锁(如果未使用互斥锁,则不执行任何操作)。

But, the main body of code in your theSingleton() function is unchanged, we have only added a control object per singleton, and a stack object in the call which manages the thread safety. 但是, theSingleton()函数中的代码主体保持不变,我们仅在每个单例中添加了一个控制对象,并在调用中添加了一个用于管理线程安全性的堆栈对象。

Note about write barriers: since the code is safe whenever ready is false, and ready can't go true until the object is initialized, the code is overall safe assuming that writes are instantly visible. 关于写障碍的注意事项:由于只要ready为false时代码就可以安全使用,并且在初始化对象之前ready不能变为真,因此假定立即可见写操作,代码就总体而言是安全的。 But, there may be a delay in ready=true being visible, since there is no write barrier after setting ready true. 但是,由于在将ready设置为true之后没有写障碍,因此ready=true可能会有所延迟。 However, during that delay safety is still maintained, since ready=false is the conservative, safe case. 但是,在此延迟期间,仍然保持安全性,因为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;
    }   
}

So seems the answer to my question best expressed Fred Larson in his comment: 因此,似乎我的问题的答案最能表达弗雷德·拉尔森(Fred Larson)的评论:

"Fast, thread-safe, lazy - pick any two." “快速,线程安全,懒惰-选择任意两个。”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM