【问题标题】:Make Meyers' Singleton thread safe and fast with lazy evaluation通过惰性求值使 Meyers 的 Singleton 线程安全快速
【发布时间】:2012-04-03 13:38:26
【问题描述】:

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

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

PS:是的,当我们使用 Singleton 指针作为类的成员时,我还阅读了很多关于线程安全的 Singletom 实现的内容,问题是关于 Singleton 的这种特殊实现,没有指针和 new 并使用惰性求值。

【问题讨论】:

  • 这里有一个提示:您遇到的实施问题可能表明您尝试做的事情是错误的。
  • 为什么不使用全局变量而不是单例?
  • @DeadMG 我认为我所问的很可能是不可能的,但我不确定,所以我在问 =)
  • 快速、线程安全、惰性 - 选择任意两个。
  • 让你的东西类线程安全。只需在您的启动代码中构造一个 thingy 实例。只做一次。不要再做任何事情了。即时单例。

标签: c++ multithreading design-patterns thread-safety singleton


【解决方案1】:

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

如果你有可用的 boost 线程,你可以使用boost::call_once 来初始化。这是线程安全的,仅在第一次初始化时需要花费。

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

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

(顺便提一下,Meyers Effective C++ 的第 47 条中提到了这个单例,这表明该标准的后续版本使其成为线程安全的,并且后来的编译器也符合它,但是它确实警告您并非所有编译器都兼容)。

【讨论】:

  • 是的,Meyers 单例的最大缺点是这些对象的非确定性破坏顺序,因此如果有任何依赖于任何其他对象,您无法保证哪个先发生,这可能会导致问题。
【解决方案2】:

好的,所以如果没有互斥锁,您根本无法做到这一点,但您可以让互斥锁变得更快。

首先声明一个类来保存互斥体和一个就绪标志(下面的InitMutex)。当GrabMutex() 被调用并且readyfalse 时,实际的互斥量被抓取。当ReleaseMutex() 被调用时,它会根据GrabMutex() 发送的标志做正确的事情。在第一次通过后,由于现在已初始化静态对象,因此 ready 变为 true,因此不需要抓取互斥体。

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

现在,在您的常规 getSingleton 函数中实例化该类。这将确保单例初始化第一次通过互斥锁,并且如果多个线程争用它们将在互斥锁上排队。但是一旦初始化了单例,ready 就会变为真,访问会很快。析构函数在return theSingleton 执行后被神奇地调用,释放互斥锁(如果互斥锁没有被占用,则什么也不做)。

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

关于写屏障的注意事项:由于代码在ready 为假时是安全的,并且ready 在对象初始化之前不能为真,因此假设写入立即可见,则代码总体上是安全的。但是,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;
    }   
}

【讨论】:

  • 我想我忘了在主题词中添加“懒惰的评价”,抱歉。
  • 我很清楚Singleton只初始化一次,在main之前初始化它是线程安全的,但不会是惰性求值。
  • 我知道你现在写的那个变种。我在 PS 中的问题中指出,我要问的是没有 new 和指针的实现。我猜之前问过我的问题,答案是“不,这是不可能的”,而你的帖子似乎回答了同样的问题)
  • 只是因为没有 new 我不需要关心 delete :-) 据我了解,当使用带有指针和的 Singleton 实现时new inside getInstance 方法我们正在计算 Sigleton 类之外的某个地方 delete 将被调用,这似乎很有可能发生内存泄漏。
  • 好的,我有一个答案 - 完全重写这个:)
【解决方案3】:

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

“快速、线程安全、惰性 - 选择任意两个。”

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 2016-03-04
    相关资源
    最近更新 更多