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

tem*_*boy 1 c++ singleton multithreading thread-safety c++11

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

class singleton final
{
public:
    static singleton& instance()
    {
        static singleton unique;
        return unique;
    }
private:
    singleton() = default;
    singleton(singleton const&) = delete;
    singleton& operator=(singleton const&) = delete;
};
Run Code Online (Sandbox Code Playgroud)

如果我有这样的代码:

singleton *p1, *p2;

auto t1 = std::thread([] { p1 = &singleton::instance(); });
auto t2 = std::thread([] { p2 = &singleton::instance(); });

t1.join();
t2.join();
Run Code Online (Sandbox Code Playgroud)

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

How*_*ant 11

在C++ 98/03中一个文件本地静态:

X& instance()
{
    static X x;
    return x;
}
Run Code Online (Sandbox Code Playgroud)

意味着你的代码会做这样的事情:

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);
}
Run Code Online (Sandbox Code Playgroud)

"__" - 带前缀的名称是编译器提供的.

但是在上面的代码中,没有任何东西阻止两个线程同时进入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);
}
Run Code Online (Sandbox Code Playgroud)

但是现在有可能将一个线程设置__instance_initialized为true并开始构造X,并进行第二个线程测试并跳过if第一个线程仍忙于构建的时间X.然后,第二个线程将向其客户端提供未初始化的内存,直到第一个线程最终完成构造.

在C++ 11中,语言规则被更改,以便编译器必须设置代码,使得第二个线程无法运行,也不会X在第一个线程成功完成构造之前开始构造.这可能意味着第二个线程必须等待任意时间才能继续...直到第一个线程完成.如果第一个线程在尝试构造时抛出异常X,则第二个线程将唤醒并尝试构建它.

以下是关于编译器如何实现该目标的Itanium ABI规范.