2

我正在使用Visual Studio 2013,它尚未实现“魔术静态”功能,因此本地静态变量初始化不是但线程安全。所以,与其使用本地静态std :: once_flag和本地静态指针的线程安全初始化静态变量

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; 
} 

它似乎工作(至少它通过了测试),但我不确定它是线程安全的,因为在这里我们有与多个线程中的静态局部变量p_fooflag的初始化相同的潜在问题。使用nullptr初始化原始指针并初始化std::once_flag似乎比调用Foo的构造函数更无害,但我想知道它是否非常安全。

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

+0

HTTPS://codereview.stackexchange。 com/ – Blacktempel

+0

请参阅[call_once on cppreference](http://en.cppreference.com/w/cpp/thread/call_once);特别是第2点。 – Simple

+0

@Simple _在上述所选函数的执行成功完成之前,组中的所有调用都没有返回,也就是说,不会通过exception._退出,但我的担忧与std :: call_once,而是关于这两行:'static Foo * p_foo = nullptr; std :: once_flag标志;' – undermind

回答

0

如果Foo& GetInstance()只是同一编译单元的一部分,则定义初始化顺序,因此它是线程安全的。然而,如果上面不是这种情况,并且多个编译单元正在引用,那么初始化顺序将取决于调用Foo& GetInstance()的调用顺序,并且如果涉及多个线程,则顺序未定义,因此不是线程安全的。

值得一看

+0

但在最后一个片段中,我只有局部静态变量。 – undermind

+0

是的,但是同时调用'Foo&GetInstance()'的两个线程可以是真/可重复的。 – Griffin

+0

@undermind在'C++ 11'中说过[看起来像](https://stackoverflow.com/questions/8102125/is-local-static-variable-initialization-thread-safe-in-c11)也许是线程安全的。现在我不太确定,也许别人可以详细说明。 – Griffin

1

到现在为止,单对象初始化最稳定的方法是schwartz_counter。这就是如何实现std::cin,cout等以及它们如何始终工作,而不管全局对象的初始化顺序如何。

它适用于所有版本的C++。

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

+0

顺便说一下,是否保证在加载时所有全局变量的初始化是从同一个线程执行的?如果我们有2个[Delay-Loaded DLLs](https://msdn.microsoft.com/en-us/library/151kt790.aspx),例如_lib1.dll_带有_func1_和_lib2.dll_函数_func2_,以及_func1_和_func2_从不同的线程同时被调用? – undermind

+0

无论如何,它并没有真正使这个习语无用,因为我们可以安全地用'std :: atomic '代替'int',所以第一个问题只是出于好奇。 – undermind

+0

@undermind schwartz计数的对象在未初始化的全局变量清零之后和main运行之前初始化。 在一个DLL中,它会在DllMain运行之前发生,我相信这发生在所有线程附件代码之前。但我必须检查,因为我很久没有使用DLL了。受不了他们! –

0

你最后的代码片断是从一个线程安全的初始化点罚款。

但是,您不清楚如何在调用GetInstance的线程中使用Foo对象。 由于您正在返回对非const对象的引用,因此我认为线程可能会修改Foo对象。请记住,您需要为此添加其他同步(例如,一mutex

如果Foo对象完全被它的构造和线程调用GetInstance将只从对象读取初始化,是没有问题的,但我会建议返回const Foo &