是什么让单身线程不安全?

时间:2014-12-31 23:23:54

标签: c++ multithreading c++11 thread-safety singleton

我在某处读到单例是线程不安全的。我试图理解为什么会这样。如果我有这样的单例对象:

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();

p1p2是否可以指向两个不同的singleton个实例?如果uniquestatic,则其"静态"性质在完全初始化之前不会生效?如果是这样,是否意味着可以同时访问静态对象的初始化,从而允许创建多个静态对象?

1 个答案:

答案 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关于编译器如何实现这一点。