关于c ++:Meyers对Singleton模式线程的实现是否安全?

Is Meyers' implementation of the Singleton pattern thread safe?

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

1
2
3
4
5
static Singleton& instance()
{
     static Singleton s;
     return s;
}

如果不是,为什么和如何使它线程安全?


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

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

gcc和vs对该特性(动态初始化和并发销毁,也称为msdn上的magic statics)的支持如下:

  • Visual Studio:自Visual Studio 2015以来支持
  • GCC:自GCC 4.3起支持

感谢@mankarse和@olen_gam的评论。


在C++ 03中,这个代码不是线程安全的。迈尔斯有一篇文章称为"C++和双重检查锁定的危险",它讨论了模式的线程安全实现,并且结论是(或多或少),(在C++ 03中)围绕实例化方法的完全锁定基本上是确保所有平台上适当并发的最简单的方式,而大多数形式的双CHE。在某些体系结构上,CKED锁模式变体可能会受到竞争条件的影响,除非指令与策略性的位置内存屏障交错。


要回答为什么它不是threadsafe的问题,并不是因为对instance()的第一个调用必须调用Singleton s的构造函数。要实现线程安全,这必须发生在关键部分,但标准中没有要求采用关键部分(迄今为止的标准对线程是完全无所谓的)。编译器通常使用静态布尔值的简单检查和增量来实现这一点——但不是在关键部分。类似于以下伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)));
}

所以这里有一个简单的线程安全单例(对于Windows)。它为Windows关键部分对象使用了一个简单的类包装器,这样我们就可以让编译器在调用main()之前自动初始化CRITICAL_SECTION。理想情况下,将使用一个真正的raii critical section类,它可以处理在持有critical section时可能发生的异常,但这超出了此答案的范围。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#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;
}

伙计-那是"让全球变得更好"的一堆废话。

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

  • 如果new Singleton()抛出,锁将不会被释放。这可以通过使用一个真正的raii锁对象而不是我这里的简单对象来解决。如果您使用Boost之类的工具来为锁提供一个独立于平台的包装器,那么这也可以帮助实现可移植性。
  • 当调用main()之后请求单例实例时,这就保证了线程的安全性—如果在此之前调用它(如在静态对象的初始化中),则由于CRITICAL_SECTION可能未初始化,因此事情可能不起作用。
  • 每次请求实例时都必须使用锁。正如我所说,这是一个简单的线程安全实现。如果你需要一个更好的(或者想知道为什么像双重检查锁技术这样的技术有缺陷),请参阅格罗的答案中链接到的文件。


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

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


正确的答案取决于编译器。它可以决定让它成为线程安全;它不是"自然"的线程安全。


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

在大多数平台上,这不是线程安全的。(附上通常的免责声明,说明C++标准不知道线程,因此,在法律上,它不说它是不是。)

If not, why [...]?

这不是因为没有什么能阻止多个线程同时执行s构造函数。

how to make it thread safe?

Scott Meyers和Andrei Alexandrescu的"C++和双重检查锁定的风险"是一篇关于线程安全单体主题的很好的论文。


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