C++ Meyers 实现的单例模式线程安全吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1661529/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 20:48:42  来源:igfitidea点击:

Is Meyers' implementation of the Singleton pattern thread safe?

c++multithreadingdesign-patterns

提问by Ankur

Is the following implementation, using lazy initialization, of Singleton(Meyers' Singleton) thread safe?

Singleton(Meyers' Singleton) 线程的以下使用延迟初始化的实现是否安全?

static Singleton& instance()
{
     static Singleton s;
     return s;
}

If not, why and how to make it thread safe?

如果不是,为什么以及如何使其线程安全?

回答by Groo

In C++11, it is thread safe. According to the standard, §6.7 [stmt.dcl] p4:

C++11 中,它是线程安全的。根据标准§6.7 [stmt.dcl] p4

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall waitfor completion of the initialization.

如果在初始化变量的同时控制进入声明,则并发执行将等待初始化完成。

GCC and VS support for the feature (Dynamic Initialization and Destruction with Concurrency, also known as Magic Statics on MSDN) is as follows:

GCC 和 VS 对该功能的支持(Dynamic Initialization and Destruction with Concurrency在 MSDN 上也称为Magic Statics)如下:

Thanks to @Mankarse and @olen_gam for their comments.

感谢@Mankarse 和@olen_gam 的评论。



In C++03, this code wasn't thread safe. There is an article by Meyers called "C++ and the Perils of Double-Checked Locking"which discusses thread safe implementations of the pattern, and the conclusion is, more or less, that (in C++03) full locking around the instantiating method is basically the simplest way to ensure proper concurrency on all platforms, while most forms of double-checked locking pattern variants may suffer from race conditions on certain architectures, unless instructions are interleaved with strategically places memory barriers.

C++03 中,此代码不是线程安全的。Meyers 发表了一篇名为“C++ 和双重检查锁定的危险”的文章,其中讨论了该模式的线程安全实现,结论或多或少是(在 C++03 中)围绕实例化方法的完全锁定基本上是确保所有平台上正确并发的最简单方法,而大多数形式的双重检查锁定模式变体在某些架构上可能会遇到竞争条件,除非指令与战略性地放置内存屏障交错。

回答by Michael Burr

To answer your question about why it's not threadsafe, it's not because the first call to instance()must call the constructor for Singleton s. To be threadsafe this would have to occur in a critical section, and but there's no requirement in the standard that a critical section be taken (the standard to date is completely silent on threads). Compilers often implement this using a simple check and increment of a static boolean - but not in a critical section. Something like the following pseudocode:

要回答关于为什么它不是线程安全的问题,这不是因为第一次调用instance()必须调用Singleton s. 为了线程安全,这必须发生在临界区中,但标准中没有要求采用临界区(迄今为止的标准对线程完全没有规定)。编译器通常使用静态布尔值的简单检查和增量来实现这一点 - 但不在临界区中。类似于以下伪代码:

static Singleton& instance()
{
    static bool initialized = false;
    static char s[sizeof( Singleton)];

    if (!initialized) {
        initialized = true;

        new( &s) Singleton(); // call placement new on s to construct it
    }

    return (*(reinterpret_cast<Singleton*>( &s)));
}

So here's a simple thread-safe Singleton (for Windows). It uses a simple class wrapper for the Windows CRITICAL_SECTION object so that we can have the compiler automatically initialize the CRITICAL_SECTIONbefore main()is called. Ideally a true RAII critical section class would be used that can deal with exceptions that might occur when the critical section is held, but that's beyond the scope of this answer.

所以这是一个简单的线程安全单例(适用于 Windows)。它为 Windows CRITICAL_SECTION 对象使用一个简单的类包装器,以便我们可以让编译器自动初始化调用CRITICAL_SECTION之前main()。理想情况下,将使用真正的 RAII 临界区类,该类可以处理保留临界区时可能发生的异常,但这超出了本答案的范围。

The fundamental operation is that when an instance of Singletonis requested, a lock is taken, the Singleton is created if it needs to be, then the lock is released and the Singleton reference returned.

基本操作是,当Singleton请求一个实例时,获取一个锁,如果需要,则创建 Singleton,然后释放锁并返回 Singleton 引用。

#include <windows.h>

class CritSection : public CRITICAL_SECTION
{
public:
    CritSection() {
        InitializeCriticalSection( this);
    }

    ~CritSection() {
        DeleteCriticalSection( this);
    }

private:
    // disable copy and assignment of CritSection
    CritSection( CritSection const&);
    CritSection& operator=( CritSection const&);
};


class Singleton
{
public:
    static Singleton& instance();

private:
    // don't allow public construct/destruct
    Singleton();
    ~Singleton();
    // disable copy & assignment
    Singleton( Singleton const&);
    Singleton& operator=( Singleton const&);

    static CritSection instance_lock;
};

CritSection Singleton::instance_lock; // definition for Singleton's lock
                                      //  it's initialized before main() is called


Singleton::Singleton()
{
}


Singleton& Singleton::instance()
{
    // check to see if we need to create the Singleton
    EnterCriticalSection( &instance_lock);
    static Singleton s;
    LeaveCriticalSection( &instance_lock);

    return s;
}

Man - that's a lot of crap to "make a better global".

伙计 - “让全球变得更好”是一大堆废话。

The main drawbacks to this implemention (if I didn't let some bugs slip through) is:

这个实现的主要缺点(如果我没有让一些错误溜走的话)是:

  • if new Singleton()throws, the lock won't be released. This can be fixed by using a true RAII lock object instead of the simple one I have here. This can also help make things portable if you use something like Boost to provide a platform independent wrapper for the lock.
  • this guarantees thread safety when the Singleton instance is requested after main()is called - if you call it before then (like in a static object's initialization) things might not work because the CRITICAL_SECTIONmight not be initialized.
  • a lock must be taken each time an instance is requested. As I said, this is a simple thread safe implementation. If you need a better one (or want to know why things like the double-check lock technique is flawed), see the papers linked to in Groo's answer.
  • 如果new Singleton()抛出,锁不会被释放。这可以通过使用真正的 RAII 锁对象而不是我这里的简单对象来解决。如果您使用 Boost 之类的东西为锁提供独立于平台的包装器,这也有助于使事情变得可移植。
  • 这保证了在main()调用Singleton 实例后请求时的线程安全性- 如果您在此之前调用它(如在静态对象的初始化中),事情可能无法工作,因为它CRITICAL_SECTION可能未初始化。
  • 每次请求实例时都必须锁定。正如我所说,这是一个简单的线程安全实现。如果您需要更好的(或想知道为什么像双重检查锁定技术这样的东西有缺陷),请参阅Groo 的回答中链接到论文

回答by deft_code

Looking at the next standard (section 6.7.4), it explians how static local initialization is thread safe. So once that section of standard is widely implemented, Meyer's Singleton will be the preferred implementation.

查看下一个标准(第 6.7.4 节),它解释了静态本地初始化如何是线程安全的。因此,一旦该部分标准得到广泛实施,Meyer 的 Singleton 将成为首选实施方案。

I disagree with many answers already. Most compilers already implement static initialization this way. The one notable exception is Microsoft Visual Studio.

我已经不同意很多答案了。大多数编译器已经以这种方式实现了静态初始化。一个值得注意的例外是 Microsoft Visual Studio。

回答by MSalters

The correct answer depends on your compiler. It can decide to makeit threadsafe; it's not "naturallly" threadsafe.

正确答案取决于您的编译器。它可以决定使其成为线程安全的;它不是“自然地”线程安全的。

回答by sbi

Is the following implementation [...] thread safe?

以下实现 [...] 线程安全吗?

On most platforms, this is not thread-safe. (Append the usual disclaimer explaining that the C++ standard doesn't know about threads, so, legally, it doesn't say whether it is or not.)

在大多数平台上,这不是线程安全的。(附加通常的免责声明,解释 C++ 标准不了解线程,因此,从法律上讲,它并没有说明它是否是。)

If not, why [...]?

如果没有,为什么[...]?

The reason it isn't is that nothing prevents more than one thread from simultaneously executing s' constructor.

不是的原因是没有什么可以阻止多个线程同时执行s' 构造函数。

how to make it thread safe?

如何使其线程安全?

"C++ and the Perils of Double-Checked Locking"by Scott Meyers and Andrei Alexandrescu is a pretty good treatise on the subject of thread-safe singletons.

Scott Meyers 和 Andrei Alexandrescu 的“C++ and the Perils of Double-Checked Locking”是一篇关于线程安全单例的很好的论文。

回答by sellibitze

As MSalters said: It depends on the C++ implementation you use. Check the documentation. As for the other question: "If not, why?" -- The C++ standard doesn't yet mention anything about threads. But the upcoming C++ version is aware of threads and it explicitly states that the initialization of static locals is thread-safe. If two threads call such a function, one thread will perform an initialization while the other will block & wait for it to finish.

正如 MSalters 所说:这取决于您使用的 C++ 实现。检查文档。至于另一个问题:“如果不是,为什么?” -- C++ 标准还没有提到任何关于线程的内容。但是即将到来的 C++ 版本知道线程并且它明确声明静态局部变量的初始化是线程安全的。如果两个线程调用这样的函数,一个线程将执行初始化,而另一个线程将阻塞并等待它完成。