在c++中高效的线程安全单例

时间:2022-09-02 07:57:02

The usual pattern for a singleton class is something like

单例类的常见模式是类似的。

static Foo &getInst()
{
  static Foo *inst = NULL;
  if(inst == NULL)
    inst = new Foo(...);
  return *inst;    
}

However, it's my understanding that this solution is not thread-safe, since 1) Foo's constructor might be called more than once (which may or may not matter) and 2) inst may not be fully constructed before it is returned to a different thread.

然而,我的理解是,这个解决方案不是线程安全的,因为1)Foo的构造函数可能被调用不止一次(这可能重要,也可能不重要);

One solution is to wrap a mutex around the whole method, but then I'm paying for synchronization overhead long after I actually need it. An alternative is something like

一种解决方案是将互斥对象包装在整个方法周围,但是在实际需要它之后,我还要为同步开销付费。另一种选择是类似的

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;    
}

Is this the right way to do it, or are there any pitfalls I should be aware of? For instance, are there any static initialization order problems that might occur, i.e. is inst always guaranteed to be NULL the first time getInst is called?

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

8 个解决方案

#1


39  

Your solution is called 'double checked locking' and the way you've written it is not threadsafe.

您的解决方案称为“双重检查锁定”,并且您编写它的方式不是线程安全的。

This Meyers/Alexandrescu paper explains why - but that paper is also widely misunderstood. It started the 'double checked locking is unsafe in C++' meme - but its actual conclusion is that double checked locking in C++ can be implemented safely, it just requires the use of memory barriers in a non-obvious place.

这篇迈耶斯/亚历山大的论文解释了原因——但这篇论文也被广泛误解。它开始了“在c++中,双重检查锁定是不安全的”,但它的实际结论是,在c++中,双重检查锁定可以安全地实现,它只需要在不明显的地方使用内存屏障。

The paper contains pseudocode demonstrating how to use memory barriers to safely implement the DLCP, so it shouldn't be difficult for you to correct your implementation.

本文包含了伪代码,演示了如何使用内存壁垒来安全地实现DLCP,因此纠正实现并不困难。

#2


72  

If you are using C++11, here is a right way to do this:

如果您正在使用c++ 11,这里有一个正确的方法:

Foo& getInst()
{
    static Foo inst(...);
    return inst;
}

According to new standard there is no need to care about this problem any more. Object initialization will be made only by one thread, other threads will wait till it complete. Or you can use std::call_once. (more info here)

根据新的标准,没有必要再关心这个问题了。对象初始化将只由一个线程完成,其他线程将等待它完成。也可以使用std::call_once。(更多信息)

#3


8  

Use pthread_once, which is guaranteed that the initialization function is run once atomically.

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

(On Mac OS X it uses a spin lock. Don't know the implementation of other platforms.)

(在Mac OS X上,它使用旋转锁。不知道其他平台的实现。

#4


8  

Herb Sutter talks about the double-checked locking in CppCon 2014.

Herb Sutter谈到了CppCon 2014中的双重检查锁定。

Below is the code I implemented in C++11 based on that:

下面是我在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;
}

you can also check complete program here: http://ideone.com/olvK13

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

#5


2  

TTBOMK, the only guaranteed thread-safe way to do this without locking would be to initialize all your singletons before you ever start a thread.

TTBOMK,唯一保证线程安全的方法是在启动线程之前初始化所有的单例。

#6


0  

Your alternative is called "double-checked locking".

您的替代方法称为“双重检查锁定”。

There could exist multi-threaded memory models in which it works, but POSIX does not guarantee one

它可以使用多线程内存模型,但是POSIX不能保证使用多线程内存模型

#7


0  

ACE singleton implementation uses double-checked locking pattern for thread safety, you can refer to it if you like.

ACE单例实现为线程安全使用了双重检查锁定模式,您可以参考它。

You can find source code here.

您可以在这里找到源代码。

#8


0  

Does TLS work here? https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++

TLS在这里工作吗?https://en.wikipedia.org/wiki/Thread-local_storage C_and_C + +

For example,

例如,

static _thread Foo *inst = NULL;
static Foo &getInst()
{
  if(inst == NULL)
    inst = new Foo(...);
  return *inst;    
 }

But we also need a way to delete it explicitly, like

但是我们还需要一种方法来显式地删除它,比如

static void deleteInst() {
   if (!inst) {
     return;
   }
   delete inst;
   inst = NULL;
}

#1


39  

Your solution is called 'double checked locking' and the way you've written it is not threadsafe.

您的解决方案称为“双重检查锁定”,并且您编写它的方式不是线程安全的。

This Meyers/Alexandrescu paper explains why - but that paper is also widely misunderstood. It started the 'double checked locking is unsafe in C++' meme - but its actual conclusion is that double checked locking in C++ can be implemented safely, it just requires the use of memory barriers in a non-obvious place.

这篇迈耶斯/亚历山大的论文解释了原因——但这篇论文也被广泛误解。它开始了“在c++中,双重检查锁定是不安全的”,但它的实际结论是,在c++中,双重检查锁定可以安全地实现,它只需要在不明显的地方使用内存屏障。

The paper contains pseudocode demonstrating how to use memory barriers to safely implement the DLCP, so it shouldn't be difficult for you to correct your implementation.

本文包含了伪代码,演示了如何使用内存壁垒来安全地实现DLCP,因此纠正实现并不困难。

#2


72  

If you are using C++11, here is a right way to do this:

如果您正在使用c++ 11,这里有一个正确的方法:

Foo& getInst()
{
    static Foo inst(...);
    return inst;
}

According to new standard there is no need to care about this problem any more. Object initialization will be made only by one thread, other threads will wait till it complete. Or you can use std::call_once. (more info here)

根据新的标准,没有必要再关心这个问题了。对象初始化将只由一个线程完成,其他线程将等待它完成。也可以使用std::call_once。(更多信息)

#3


8  

Use pthread_once, which is guaranteed that the initialization function is run once atomically.

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

(On Mac OS X it uses a spin lock. Don't know the implementation of other platforms.)

(在Mac OS X上,它使用旋转锁。不知道其他平台的实现。

#4


8  

Herb Sutter talks about the double-checked locking in CppCon 2014.

Herb Sutter谈到了CppCon 2014中的双重检查锁定。

Below is the code I implemented in C++11 based on that:

下面是我在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;
}

you can also check complete program here: http://ideone.com/olvK13

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

#5


2  

TTBOMK, the only guaranteed thread-safe way to do this without locking would be to initialize all your singletons before you ever start a thread.

TTBOMK,唯一保证线程安全的方法是在启动线程之前初始化所有的单例。

#6


0  

Your alternative is called "double-checked locking".

您的替代方法称为“双重检查锁定”。

There could exist multi-threaded memory models in which it works, but POSIX does not guarantee one

它可以使用多线程内存模型,但是POSIX不能保证使用多线程内存模型

#7


0  

ACE singleton implementation uses double-checked locking pattern for thread safety, you can refer to it if you like.

ACE单例实现为线程安全使用了双重检查锁定模式,您可以参考它。

You can find source code here.

您可以在这里找到源代码。

#8


0  

Does TLS work here? https://en.wikipedia.org/wiki/Thread-local_storage#C_and_C++

TLS在这里工作吗?https://en.wikipedia.org/wiki/Thread-local_storage C_and_C + +

For example,

例如,

static _thread Foo *inst = NULL;
static Foo &getInst()
{
  if(inst == NULL)
    inst = new Foo(...);
  return *inst;    
 }

But we also need a way to delete it explicitly, like

但是我们还需要一种方法来显式地删除它,比如

static void deleteInst() {
   if (!inst) {
     return;
   }
   delete inst;
   inst = NULL;
}