使用本地静态std :: once_flag和本地静态指针对静态变量进行线程安全初始化

时间:2017-09-13 08:09:18

标签: c++ c++11 visual-studio-2013 thread-safety static-variables

我使用的是Visual Studio 2013,它没有"魔法静电"功能尚未实现,因此本地静态变量初始化还不是线程安全的。所以,而不是

Foo& GetInstance()
{
    static Foo foo;
    return foo;
}

我这样做:

std::unique_ptr<Foo> gp_foo;
std::once_flag g_flag;

Foo& GetInstance()
{
    std::call_once(g_flag, [](){ gp_foo = std::make_unique<Foo>(); });    
    return *gp_foo;
}

但我不喜欢拥有gp_foog_flag个全局变量的想法(首先,不同翻译单元中静态变量初始化顺序的问题;第二,我想仅在我们需要它们时初始化变量,即在第一次调用GetInstance()之后,所以我实现了以下内容:

Foo& GetInstance()
{
    // I replaced a smart pointer 
    // with a raw one just to be more "safe"
    // not sure such replacing is really needed
    static Foo *p_foo = nullptr;

    static std::once_flag flag;
    std::call_once(flag, [](){ p_foo = new Foo; });    
    return *p_foo;
}

它似乎有效(至少它通过了测试),但我不确定它是否是线程安全的,因为这里我们有静态局部变量初始化的潜在问题{多个线程中的{1}}和p_foo。使用flag初始化原始指针和初始化nullptr似乎比调用std::once_flag的构造函数更无辜,但我想知道它是否真的安全。

那么,最后一段代码片段有什么问题吗?

3 个答案:

答案 0 :(得分:1)

到目前为止,最稳定的单例对象初始化方法是schwartz_counter。无论全局对象的初始化顺序如何,std::cincout等的实现方式以及它们如何始终有效。

适用于所有版本的c ++。

https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Nifty_Counter

答案 1 :(得分:0)

如果Foo& GetInstance()只是同一个编译单元的一部分,则定义初始化顺序,因此它是线程安全的。

但是,如果不是这种情况并且多个编译单元正在引用那么初始化顺序将取决于对Foo& GetInstance()的调用的顺序,并且如果涉及多个线程,那么顺序是未定义的,因此不是线程安全

值得检查

答案 2 :(得分:0)

从线程安全的初始化角度来看,您的上一个代码段很好。

但是,目前尚不清楚如何在调用Foo的线程中使用GetInstance对象。 由于您要返回对非const对象的引用,我认为线程可能会修改Foo对象。 请记住,您需要额外的同步(例如a mutex

如果Foo对象由其构造函数完全初始化,并且调用GetInstance的线程只读取对象,则没有问题,但我建议返回const Foo &