我在某处读到单例是线程不安全的。我试图理解为什么会这样。如果我有这样的单例对象:
class singleton final
{
public:
static singleton& instance()
{
static singleton unique;
return unique;
}
private:
singleton() = default;
singleton(singleton const&) = delete;
singleton& operator=(singleton const&) = delete;
};
如果我有这样的代码:
singleton *p1, *p2;
auto t1 = std::thread([] { p1 = &singleton::instance(); });
auto t2 = std::thread([] { p2 = &singleton::instance(); });
t1.join();
t2.join();
p1
和p2
是否可以指向两个不同的singleton
个实例?如果unique
为static
,则其"静态"性质在完全初始化之前不会生效?如果是这样,是否意味着可以同时访问静态对象的初始化,从而允许创建多个静态对象?
答案 0 :(得分:11)
在C ++ 98/03中一个本地静态文件:
X& instance()
{
static X x;
return x;
}
意味着您的代码会执行以下操作:
bool __instance_initialized = false;
alignas(X) char __buf_instance[sizeof(X)];
// ...
X& instance()
{
if (!__instance_initialized)
{
::new(__buf_instance) X;
__instance_initialized = true;
}
return *static_cast<X*>(__buf_instance);
}
编辑提供&#34; __&#34; - 前缀名称。
但是在上面的代码中,没有任何东西阻止两个线程同时进入if
,并且两个线程都试图同时构造X
。编译器可能会尝试通过编写来解决这个问题:
bool __instance_initialized = false;
alignas(X) char __buf_instance[sizeof(X)];
// ...
X& instance()
{
if (!__instance_initialized)
{
__instance_initialized = true;
::new(__buf_instance) X;
}
return *static_cast<X*>(__buf_instance);
}
但是现在有一个线程可以将__instance_initialized
设置为true并开始构造X
,并且第二个线程测试并跳过if
,而第一个线程是还在忙着构建X
。然后第二个线程会向其客户端显示未初始化的内存,直到第一个线程最终完成构建。
在C ++ 11中,语言规则被更改,以便编译器必须设置代码,使得第二个线程无法运行,也不会开始构造X
,直到第一个线程成功完成构造。这可能意味着第二个线程必须等待任意时间才能继续...直到第一个线程完成。如果第一个线程在尝试构造X
时抛出异常,则第二个线程将被唤醒并尝试构建它。
Here is the Itanium ABI specification关于编译器如何实现这一点。