C++中高效的线程安全单例

use*_*715 67 c++ singleton pthreads thread-safety

单身类的通常模式就像

static Foo &getInst()
{
  static Foo *inst = NULL;
  if(inst == NULL)
    inst = new Foo(...);
  return *inst;    
}
Run Code Online (Sandbox Code Playgroud)

但是,我的理解是这个解决方案不是线程安全的,因为1)Foo的构造函数可能被多次调用(可能或可能不重要)和2)inst在返回到不同的线程之前可能没有完全构造.

一种解决方案是围绕整个方法包装一个互斥锁,但是在我真正需要它之后很长时间我就要付出同步开销.另一种选择是

static Foo &getInst()
{
  static Foo *inst = NULL;
  if(inst == NULL)
  {
    pthread_mutex_lock(&mutex);
    if(inst == NULL)
      inst = new Foo(...);
    pthread_mutex_unlock(&mutex);
  }
  return *inst;    
}
Run Code Online (Sandbox Code Playgroud)

这是正确的做法,还是我应该注意哪些陷阱?例如,是否存在可能发生的静态初始化顺序问题,即在第一次调用getInst时,inst总是保证为NULL?

Sat*_*Sat 84

如果您使用的是C++ 11,这是一种正确的方法:

Foo& getInst()
{
    static Foo inst(...);
    return inst;
}
Run Code Online (Sandbox Code Playgroud)

根据新标准,不再需要关心这个问题.对象初始化只能由一个线程完成,其他线程将等待它完成.或者你可以使用std :: call_once.(更多信息在这里)

  • 遗憾的是,这在VS2013中不是线程安全的,请参阅"Magic Statics":http://msdn.microsoft.com/en-gb/library/hh567368.aspx (8认同)
  • 为避免混淆,您可以将静态添加到函数声明中,或者明确声明它是非成员函数. (6认同)
  • VS 14似乎解决了这个问题 - http://blogs.msdn.com/b/vcblog/archive/2014/06/03/visual-studio-14-ctp.aspx (3认同)
  • 这是我希望人们能够实现的C ++ 11解决方案。 (2认同)

Joe*_*oeG 42

您的解决方案称为"双重检查锁定",您编写它的方式不是线程安全的.

这篇Meyers/Alexandrescu论文解释了为什么 - 但这篇论文也被广泛误解.它开始了'双重检查锁定在C++中的不安全'模因 - 但它的实际结论是C++中的双重检查锁定可以安全地实现,它只需要在非显而易见的地方使用内存屏障.

本文包含伪代码,演示如何使用内存屏障来安全地实现DLCP,因此您应该不难纠正您的实现.


qqi*_*row 11

Herb Sutter谈到了CppCon 2014中的双重锁定.

下面是我在C++ 11中实现的代码,基于:

class Foo {
public:
    static Foo* Instance();
private:
    Foo() {}
    static atomic<Foo*> pinstance;
    static mutex m_;
};

atomic<Foo*> Foo::pinstance { nullptr };
std::mutex Foo::m_;

Foo* Foo::Instance() {
  if(pinstance == nullptr) {
    lock_guard<mutex> lock(m_);
    if(pinstance == nullptr) {
        pinstance = new Foo();
    }
  }
  return pinstance;
}
Run Code Online (Sandbox Code Playgroud)

您也可以在这里查看完整的程序:http://ideone.com/olvK13

  • 实例函数中的一个简单的`静态Foo foo;`和`return&foo;`就足够了; `static`初始化在C++ 11中是线程安全的.但更喜欢引用指针. (4认同)
  • 为什么已经有了C ++ 11时为什么要进行双重检查? (2认同)

ken*_*ytm 8

使用pthread_once,保证初始化函数以原子方式运行一次.

(在Mac OS X上它使用自旋锁.不知道其他平台的实现.)