![](/img/trans.png)
[英]std::call_once vs std::mutex for thread-safe initialization
[英]Is std::call_once reentrant and thread safe?
std :: call_once是線程安全的,但是它也可以重入嗎?
我使用VS2012(Debug和Release)進行的測試表明,從單個線程遞歸調用std::call_once
是可以的,但如果調用是在不同的線程上進行的,則會導致死鎖。 這是std::call_once
的已知限制嗎?
#include "stdafx.h"
#include <iostream>
#include <mutex>
#include <thread>
void Foo()
{
std::cout << "Foo start" << std::endl;
std::once_flag flag;
std::call_once( flag, [](){
std::cout << "Hello World!" << std::endl;
});
std::cout << "Foo end" << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
// Single threaded Works
{
std::once_flag fooFlag;
std::call_once( fooFlag, Foo);
}
// Works
// Threaded version, join outside call_once
{
std::once_flag fooFlag;
std::thread t;
std::call_once( fooFlag, [&t](){
t = std::thread(Foo);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
});
t.join();
}
// Dead locks
// Threaded version, join inside call_once
{
std::once_flag fooFlag;
std::call_once( fooFlag, [](){
auto t = std::thread(Foo);
t.join();
});
}
return 0;
}
看起來像std:call_once
正在鎖定一個靜態互斥鎖,在該函數退出之前它不會被解鎖。 在單線程情況下它可以工作,因為在第二次調用時線程已經有鎖。 在線程版本上,它將阻塞,直到第一個調用退出。
我還注意到,如果將Foo()
函數中的std::once_flag
標志更改為static
,則仍會發生死鎖。
最接近標准的是17.6.5.8 [reentrancy] :
1 - 除非在本標准中明確指定,否則它是實現定義的,可以遞歸地重新輸入標准C ++庫中的哪些函數。
不幸的是, call_once
的規范沒有說明它是遞歸的(或者是跨線程遞歸的),並且線程支持庫前導碼也沒有說明這個主題。
也就是說,VC ++實現顯然不是最理想的,特別是因為可以使用condition_variable
編寫一個userland版本的call_once
:
#include <mutex>
#include <condition_variable>
struct once_flag {
enum { INIT, RUNNING, DONE } state = INIT;
std::mutex mut;
std::condition_variable cv;
};
template<typename Callable, typename... Args>
void call_once(once_flag &flag, Callable &&f, Args &&...args)
{
{
std::unique_lock<std::mutex> lock(flag.mut);
while (flag.state == flag.RUNNING) {
flag.cv.wait(lock);
}
if (flag.state == flag.DONE) {
return;
}
flag.state = flag.RUNNING;
}
try {
f(args...);
{
std::unique_lock<std::mutex> lock(flag.mut);
flag.state = flag.DONE;
}
flag.cv.notify_all();
}
catch (...) {
{
std::unique_lock<std::mutex> lock(flag.mut);
flag.state = flag.INIT;
}
flag.cv.notify_one();
throw;
}
}
請注意,這是一個細粒度的實現; 也可以編寫一個粗粒度的實現,它在所有一次標志中使用一對互斥和條件變量,但是你需要確保在拋出異常時通知所有等待的線程(例如,libc ++這樣做) 。
為了提高效率,你可以使once_flag::state
成為一個原子並使用雙重檢查鎖定; 為簡潔起見,這里省略了。
這里不是“答案”,但至少確認Visual Studio方面的某些內容與您的使用模式有關。
我在coliru.stacked-crooked.com上編譯並運行了這個示例,它似乎按預期執行,沒有任何死鎖。
這是編譯行:
g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
這是輸出:
Foo start
Hello World!
Foo end
Foo start
Hello World!
Foo end
Foo start
Hello World!
Foo end
在我指責庫實現之前,我個人認為我自己的代碼存在問題。
我確實看到Visual Studio的call_once實現有一些類似的問題:
http://www.unknownerror.org/Problem/index/1100762158/why-does-this-c-static-singleton-never-stop/
我有興趣知道Debug或Release中是否出現此問題或兩者兼而有之?
畢竟ISO標准確實說call_once是線程安全的!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.