使用延迟初始化的Singleton
(Meyers'Seingleton)线程的以下实现是否安全?
static Singleton& instance()
{
static Singleton s;
return s;
}
如果没有,为什么以及如何使其线程安全?
答案 0 :(得分:144)
在C++11中,它是线程安全的。根据{{3}},§6.7 [stmt.dcl] p4
:
如果控制进入 在初始化变量的同时声明,并发执行应等待以完成初始化。
GCC和VS对该功能的支持(standard,也称为Dynamic Initialization and Destruction with Concurrency)如下:
感谢@Mankarse和@olen_gam的评论。
在GCC 4.3中,此代码不是线程安全的。 Meyers有一篇名为C++03的文章讨论了模式的线程安全实现,结论或多或少,(在C ++ 03中)完全锁定实例化方法基本上是最简单的方法确保在所有平台上实现正确的并发性,而大多数形式的双重检查锁定模式变体可能会受到"C++ and the Perils of Double-Checked Locking"的影响,除非指令与策略性交错存放内存障碍。
答案 1 :(得分:19)
要回答关于为什么它不是线程安全的问题,这不是因为第一次调用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)));
}
所以这是一个简单的线程安全的Singleton(适用于Windows)。它为Windows CRITICAL_SECTION对象使用一个简单的类包装器,这样我们就可以让编译器在调用CRITICAL_SECTION
之前自动初始化main()
。理想情况下,将使用真正的RAII关键部分类来处理临界部分可能发生的异常,但这超出了本答案的范围。
基本操作是当请求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;
}
男人 - 为了“让全球变得更好”,这是很多废话。
这个实现的主要缺点(如果我没有让一些错误漏掉)是:
new Singleton()
抛出,则不会释放锁定。这可以通过使用真正的RAII锁定对象而不是我在这里的简单锁定对象来修复。如果您使用像Boost这样的东西为锁定提供独立于平台的包装器,这也有助于使事物变得可移植。main()
后请求Singleton实例时保证线程安全 - 如果在此之前调用它(就像在静态对象的初始化中那样),事情可能不起作用,因为CRITICAL_SECTION
可能不是初始化。答案 2 :(得分:9)
查看下一个标准(第6.7.4节),它探讨了静态本地初始化如何是线程安全的。因此,一旦标准的这一部分被广泛实施,Meyer的Singleton将成为首选实现。
我已经不同意许多答案了。大多数编译器已经以这种方式实现静态初始化一个值得注意的例外是Microsoft Visual Studio。
答案 3 :(得分:6)
正确答案取决于您的编译器。它可以决定使线程安全;它不是“天生的”线程安全。
答案 4 :(得分:5)
以下实现线程是否安全?
在大多数平台上,这不是线程安全的。 (附加通常的免责声明,解释C ++标准不知道线程,因此,从法律上讲,它不会说是否是。)
如果没有,为什么[...]?
它不是因为没有什么能阻止多个线程同时执行s
'构造函数。
Scott Meyers和Andrei Alexandrescu的如何使其线程安全?
"C++ and the Perils of Double-Checked Locking"是关于线程安全单身人士的一篇非常好的论文。
答案 5 :(得分:2)
正如MSalters所说:这取决于您使用的C ++实现。查看文档。至于另一个问题:“如果不是,为什么?” - C ++标准尚未提及有关线程的任何内容。但即将推出的C ++版本知道线程,它明确指出静态本地的初始化是线程安全的。如果两个线程调用这样的函数,则一个线程将执行初始化而另一个线程将阻塞&amp;等待它完成。