2017-09-13 221 views
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 &