使用Singleton
(Meyers'Singleton)线程的延迟初始化的以下实现安全吗?
static Singleton& instance()
{
static Singleton s;
return s;
}
如果没有,为什么以及如何使它线程安全?
使用Singleton
(Meyers'Singleton)线程的延迟初始化的以下实现安全吗?
static Singleton& instance()
{
static Singleton s;
return s;
}
如果没有,为什么以及如何使它线程安全?
Answers:
在C ++ 11中,它是线程安全的。根据该标准,§6.7 [stmt.dcl] p4
:
如果在初始化变量的同时控件同时输入了声明,则并发执行应等待初始化完成。
GCC和VS对功能(具有并发的动态初始化和销毁,在MSDN上也称为Magic Statics)的支持如下:
感谢@Mankarse和@olen_gam的评论。
在C ++ 03中,此代码不是线程安全的。Meyers撰写了一篇名为“ C ++和双重检查锁定的风险”的文章,该文章讨论了该模式的线程安全实现,结论是(在C ++ 03中)或多或少地围绕实例化方法进行了完全锁定基本上,这是确保所有平台上适当的并发性的最简单方法,而大多数形式的双重检查锁定模式变体在某些体系结构上可能会遭受竞争条件的影响,除非指令与策略性地放置内存屏障交错。
要回答有关为什么它不是线程安全的问题,不是因为第一次调用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
before main()
调用。理想情况下,将使用真正的RAII关键部分类,该类可以处理持有关键部分时可能发生的异常,但这超出了此答案的范围。
基本操作是,当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
可能未初始化。new Singleton()
抛出会怎样?
new Singleton()
抛出异常,则锁肯定存在问题。应该使用适当的RAII锁类,例如lock_guard
Boost中的类。我希望该示例或多或少是独立的,并且已经有点像怪物了,所以我放弃了异常安全性(但大声疾呼)。也许我应该解决此问题,以使此代码不会在不适当的地方被剪切粘贴。
下列实现线程安全吗?
在大多数平台上,这不是线程安全的。(附加通常的免责声明,解释C ++标准不了解线程,因此从法律上讲,它不会说是否存在线程。)
如果没有,为什么[...]?
不是这样的原因是没有什么可以阻止一个以上的线程同时执行s
'构造函数。
如何使其线程安全?
Scott Meyers和Andrei Alexandrescu 撰写的“ C ++和双重检查锁定的危险”是关于线程安全单例的相当不错的论文。