本文根據眾多互聯網博客內容整理后形成,引用內容的版權歸原始作者所有,僅限于學習研究使用,不得用于任何商業用途。
互斥
互斥是多線程系統中用于控制訪問的一個原對象(primitive object)。下面的例子給出了它最基本的用法:
std::mutex m;
int sh; //共享數據
// …
m.lock();
// 對共享數據進行操作:
sh += 1;
m.unlock();
在任何時刻,最多只能有一個線程執行到lock()和unlock()之間的區域(通常稱為臨界區)。當第一個線程正在臨界區執行時,后續執行到m.lock()的線程將會被阻塞直到第一個進程執行到m.unlock()。這個過程比較簡單,但是如何正確使用互斥并不簡單。錯誤地使用互斥將會導致一系列嚴重后果。大家可以設想以下情形所導致的后果:一個線程只進行了lock()而沒有執行相應unlock(); 一個線程對同一個mutex對象執行了兩次lock()操作;一個線程在等待unlock()操作時被阻塞了很久;一個線程需要對兩個mutex對象執行lock()操作后才能執行后續任務。可以在很多書(譯者注:通常操作系統相關書籍中會講到)中找到這些問題的答案。在這里(包括Locks section一節)所給出的都是一些入門級別的。
除了lock(),mutex還提供了try_lock()操作。線程可以借助該操作來嘗試進入臨界區,這樣一來該線程不會在失敗的情況下被阻塞。下面例子給出了try_lock()的用法:
std::mutex m;
int sh; //共享數據
// …
if (m.try_lock()) {
//操作共享數據
sh += 1;
m.unlock();
}
else {
//可能在試圖進入臨界區失敗后執行其它代碼
}
recursive_mutex是一種能夠被同一線程連續鎖定多次的mutex。下面是recursive_mutex的一個實例:
std::recursive_mutex m;
int sh; //共享數據
//..
void f(int i)
{
//…
m.lock();
//對共享數據進行操作
sh += 1;
if (–i>0) f(i); //注意:這里對f(i)進行了遞歸調用,
//將導致在m.unlock()之前多次執行m.lock()
m.unlock();
//…
}
對于這點,我曾經夸耀過并且用f()調用它自身。一般地,代碼會更加微妙。這是因為代碼中經常會有間接遞歸調用。比如f()調用g(),而g()又調用了h(),最后h()又調用了f(),這樣就形成了一個間接遞歸。
如果我想在未來的10秒內進入到一個mutex所劃定的臨界區,該如果實現? timed_mutex類可以解決這個問題。事實上,關于它的使用可以被看做是關聯了時間限制的try_lock()的一個特例。
std::timed_mutex m;
int sh; //共享數據
//…
if ( m.try_lock_for(std::chrono::seconds(10))) {
//對共享數據進行操作
sh += 1;
m.unlock();
}
else {
//進入臨界區失敗,在此執行其它代碼
}
try_lock_for()的參數是一個用相對時間表示的duration。如果你不想這么做而是想等到一個固定的時間點:一個time_point,你可以使用try_lock_until():
std::timed_mutex m;
int sh; //共享數據
// …
if ( m.try_lock_until(midnight)) {
//對共享數據進行操作
sh += 1;
m.unlock();
}
else {
//進入臨界區失敗,在此執行其它代碼
}
這里使用midnight是一個冷笑話:對于mutex級別的操作,相應的時間是毫秒級別的而不是小時。
當然地,C++0x中也有recursive_timed_mutex。
mutex可以被看做是一個資源(因為它經常被用來代表一種真實的資源),并且當它對至少兩個線程可見時它才是有用的。必然地,mutex不能被復制或者移動(正如你不能復制一個硬件的輸入寄存器)。
令人驚訝地,實際中經常很難做到lock()s與unlock()s的匹配。設想一下那些復雜的控制結構,錯誤以及異常,要做到匹配的確比較困難。如果你可以選擇使用locks去管理你的互斥,這將為你和你的用戶節省大量的時間,再也不用熬夜通宵徹夜無眠了。(that will save you and your users a lot of sleep??)。
std::future和std::promise
并行開發挺復雜的,特別是在試圖用好線程和鎖的過程中。如果要用到條件變量或std-atomics(一種無鎖開發方式),那就更復雜了。C++0x提供了future和promise來簡化任務線程間的返回值操作;同時為啟動任務線程提供了packaged_task以方便操作。其中的關鍵點是允許2個任務間使用無(顯式)鎖的方式進行值傳遞;標準庫幫你高效的做好這些了。基本思路很簡單:當一個任務需要向父線程(啟動它的線程)返回值時,它把這個值放到promise中。之后,這個返回值會出現在和此promise關聯的future中。于是父線程就能讀到返回值。更簡單點的方法,參看async()。
標準庫中提供了3種future:普通future和為復雜場合使用的shared_future和atomic_future。在本主題中,只展示了普通future,它已經完全夠用了。如果我們有一個future
f,通過get()可以獲得它的值:
X v = f.get(); // if necessary wait for the value to get computed
如果它的返回值還沒有到達,調用線程會進行阻塞等待。要是等啊等啊,等到花兒也謝了的話,get()會拋出異常的(從標準庫或等待的線程那個線程中拋出)。
如果我們不需要等待返回值(非阻塞方式),可以簡單詢問一下future,看返回值是否已經到達:
if (f.wait_for(0))
{
// there is a value to get()
// do something
}
else
{
// do something else
}
但是,future最主要的目的還是提供一個簡單的獲取返回值的方法:get()。
promise的主要目的是提供一個”put”(或”get”,隨你)操作,以和future的get()對應。future和promise的名字是有歷史來歷的,是一個雙關語。感覺有點別扭?請別怪我。
promise為future傳遞的結果類型有2種:傳一個普通值或者拋出一個異常
try {
X res;
// compute a value for res
p.set_value(res);
}
catch (…) { // oops: couldn’t compute res
p.set_exception(std::current_exception());
}
到目前為止還不錯,不過我們如何匹配future/promise對呢?一個在我的線程,另一個在別的啥線程中嗎?是這樣:既然future和promise可以被到處移動(不是拷貝),那么可能性就挺多的。最普遍的情況是父子線程配對形式,父線程用future獲取子線程promise返回的值。在這種情況下,使用async()是很優雅的方法。
packaged_task提供了啟動任務線程的簡單方法。特別是它處理好了future和promise的關聯關系,同時提供了包裝代碼以保證返回值/異常可以放到promise中,示例代碼:
void comp(vector& v)
{
// package the tasks:
// (the task here is the standard
// accumulate() for an array of doubles):
packaged_task pt0{std::accumulate};
packaged_task pt1{std::accumulate};
auto f0 = pt0.get_future(); // get hold of the futures
auto f1 = pt1.get_future();
pt0(&v[0],&v[v.size()/2],0); // start the threads
pt1(&[v.size()/2],&v[size()],0);
return f0.get()+f1.get(); // get the results
}
async()
async()函數是一個簡單任務的”啟動”(launcher)函數。
下邊是一種優于傳統的線程+鎖的并發編程方法示例(譯注:山寨map-reduce哦):
template<class T,class V> struct Accum { // 簡單的積函數對象
T* b;
T* e;
V val;
Accum(T* bb, T* ee, const V& v) : b{bb}, e{ee}, val{vv} {}
V operator() ()
{ return std::accumulate(b,e,val); }
};
void comp(vector<double>& v)
// 如果v夠大,則產生很多任務 {
if (v.size()<10000)
return std::accumulate(v.begin(),v.end(),0.0);
auto f0 {async(Accum{&v[0],&v[v.size()/4],0.0})};
auto f1 {async(Accum{&v[v.size()/4],&v[v.size()/2],0.0})};
auto f2 {async(Accum{&v[v.size()/2],&v[v.size()*3/4],0.0})};
auto f3 {async(Accum{&v[v.size()*3/4],&v[v.size()],0.0})};
return f0.get()+f1.get()+f2.get()+f3.get();
}
盡管這只是一個簡單的并發編程示例(留意其中的”magic number“),不過我們可沒有使用線程,鎖,緩沖區等概念。f*變量的類型(即async()的返回值)是”std::future”類型。future.get()表示如果有必要的話則等待相應的線程(std::thread)運行結束。async的工作是根據需要來啟動新線程,而future的工作則是等待新線程運行結束。”簡單性”是async/future設計中最重視的一個方面;future一般也可以和線程一起使用,不過不要使用async()來啟動類似I/O操作,操作互斥體(mutex),多任務交互操作等復雜任務。async()背后的理念和range-for statement很類似:簡單事兒簡單做,把復雜的事情留給一般的通用機制來搞定吧。
async()可以啟動一個新線程或者復用一個它認為合適的已有線程(非調用線程即可)(譯注:語義上并發即可,不關心具體的調度策略。和go語義中的goroutines有點像)。后者從用戶視角看更有效一些(只對簡單任務而言)。
線程(thread)
線程(譯注:大約是C++11中最激動人心的特性了)是一種對程序中的執行或者計算的表述。跟許多現代計算一樣,C++11中的線程之間能夠共享地址空間。從這點上來看,它不同于進程:進程一般不會直接跟其它進程共享數據。在過去,C++針對不同的硬件和操作系統有著不同的線程實現版本。如今,C++將線程加入到了標準件庫中:一個標準線程ABI。
許多大部頭書籍以及成千上萬的論文都曾涉及到并發、并行以及線程。在這一條FAQ里幾乎不涉及這些內容。事實上,要做到清楚地思考并發非常難。如果你想編寫并發程序,請至少看一本書。不要依賴于一本手冊、一個標準或者一條FAQ。
在用一個函數或者函數對象(包括lambda)構造std::thread時,一個線程便啟動了。
#include <thread>
void f();
struct F {
void operator()();
};
int main()
{
std::thread t1{f}; // f() 在一個單獨的線程中執行
std::thread t2{F()}; // F()() 在一個單獨的線程中執行
}
然而,無論f()和F()執行任何功能,都不能給出有用的結果。這是因為程序可能會在t1執行f()之前或之后以及t2執行F()之前或之后終結。我們所期望的是能夠等到兩個任務都完成,這可以通過下述方法來實現:
int main()
{
std::thread t1{f}; // f() 在一個單獨的線程中執行
std::thread t2{F()}; // F()()在一個單獨的線程中執行
t1.join(); // 等待t1
t2.join(); // 等待t2
}
上面例子中的join()保證了在t1和t2完成后程序才會終結。這里”join”的意思是等待線程返回后再終結。
通常我們需要傳遞一些參數給要執行的任務。例如:
void f(vector<double>&);
struct F {
vector<double>& v;
F(vector<double>& vv) :v{vv} { }
void operator()();
};
int main(){
// f(some_vec) 在一個單獨的線程中執行
std::thread t1{std::bind(f,some_vec)};
// F(some_vec)() 在一個單獨的線程中執行
std::thread t2{F(some_vec)};
t1.join();
t2.join();
}
上例中的標準庫函數bind會將一個函數對象作為它的參數。
通常我們需要在執行完一個任務后得到返回的結果。對于那些簡單的對返回值沒有概念的,我建議使用std::future。另一種方法是,我們可以給任務傳遞一個參數,從而這個任務可以把結果存在這個參數中。例如:
void f(vector<double>&, double* res); // 將結果存在res中
struct F {
vector<double>& v;
double* res;
F(vector<double>& vv, double* p) :v{vv}, res{p} { }
void operator()(); //將結果存在res中
};
int main()
{
double res1;
double res2;
// f(some_vec,&res1) 在一個單獨的線程中執行
std::thread t1{std::bind(f,some_vec,&res1)};
// F(some_vec,&res2)() 在一個單獨的線程中執行
std::thread t2{F(some_vec,&res2)};
t1.join();
t2.join();
std::cout << res1 << " " << res2 << ‘\n’;
}
但是關于錯誤呢?如果一個任務拋出了異常應該怎么辦?如果一個任務拋出一個異常并且它沒有捕獲到這個異常,這個任務將會調用std::terminate()。調用這個函數一般意味著程序的結束。我們常常會為避免這個問題做諸多嘗試。std::future可以將異常傳送給父線程(這正是我喜歡future的原因之一)。否則,返回錯誤代碼。
除非一個線程的任務已經完成了,當一個線程超出所在的域的時候,程序會結束。很明顯,我們應該避免這一點。
沒有辦法來請求(也就是說盡量文雅地請求它盡可能早的退出)一個線程結束或者是強制(也就是說殺死這個線程)它結束。下面是可供我們選擇的操作:
- 設計我們自己的協作的中斷機制(通過使用共享數據來實現。父線程設置這個數據,子線程檢查這個數據(子線程將會在該數據被設置后很快退出))。
- 使用thread::native_handle()來訪問線程在操作系統中的符號
- 殺死進程(std::quick_exit())
- 殺死程序(std::terminate())
這些是委員會能夠統一的所有的規則。特別地,來自POSIX的代表強烈地反對任何形式的“線程取消”。然而許多C++的資源模型都依賴于析構器。對于每種系統和每種可能的應有并沒有完美的解決方案。
線程中的一個基本問題是數據競爭。也就是當在統一地址空間的兩個線程獨立訪問一個對象時將會導致沒有定義的結果。如果一個(或者兩個)對對象執行寫操作,而另一個(或者兩個)對該對象執行讀操作,兩個線程將在誰先完成操作方面進行競爭。這樣得到的結果不僅僅是沒定義的,而且常常無法預測最后的結果。為解決這個問題,C++0x提供了一些規則和保證從而能夠讓程序員避免數據競爭。
- C++標準庫函數不能直接或間接地訪問正在被其它線程訪問的對象。一種例外是該函數通過參數(包括this)來直接或間接訪問這個對象。
- C++標準庫函數不能直接或間接修改正在被其它線程訪問的對象。一種例外是該函數通過非const參數(包括this)來直接或間接訪問這個對象。
- C++標準函數庫的實現需要避免在同時修改統一序列中的不同成員時的數據競爭。
除非已使用別的方式做了聲明,多個線程同時訪問一個流對象、流緩沖區對象,或者C庫中的流可能會導致數據競爭。因此除非你能夠控制,絕不要讓兩個線程來共享一個輸出流。
你可以
- 等待一個線程一定的時間
- 通過互斥來控制對數據的訪問
- 通過鎖來控制對數據的訪問
- 使用條件變量來等待另一個線程的行為
- 通過future來從線程中返回值
std::condition_variable 類介紹
std::condition_variable 是條件變量,更多有關條件變量的定義參考維基百科。Linux 下使用 Pthread 庫中的 pthread_cond_*() 函數提供了與條件變量相關的功能, Windows 則參考 MSDN。
當 std::condition_variable 對象的某個 wait 函數被調用的時候,它使用 std::unique_lock(封裝 std::mutex) 來鎖住當前線程。當前線程會一直被阻塞,直到另外一個線程在相同的 std::condition_variable 對象上調用了 notification 函數來喚醒當前線程。
std::condition_variable 對象通常使用 std::unique_lock<std::mutex> 來等待,如果需要使用另外的 lockable 類型,可以使用 std::condition_variable_any 類,本文后面會講到 std::condition_variable_any 的用法。
首先我們來看一個簡單的例子:
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx; // 全局互斥鎖.
std::condition_variable cv; // 全局條件變量.
bool ready = false; // 全局標志位.
void do_print_id(int id)
{
std::unique_lock <std::mutex> lck(mtx);
while (!ready) // 如果標志位不為 true, 則等待...
cv.wait(lck); // 當前線程被阻塞, 當全局標志位變為 true 之后,
// 線程被喚醒, 繼續往下執行打印線程編號id.
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock <std::mutex> lck(mtx);
ready = true; // 設置全局標志位為 true.
cv.notify_all(); // 喚醒所有線程.
}
int main()
{
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(do_print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // go!
for (auto & th:threads)
th.join();
return 0;
}
執行結果如下:
concurrency ) ./ConditionVariable-basic1
10 threads ready to race...
thread 1
thread 0
thread 2
thread 3
thread 4
thread 5
thread 6
thread 7
thread 8
thread 9
好了,對條件變量有了一個基本的了解之后,我們來看看 std::condition_variable 的各個成員函數。
std::condition_variable 構造函數
default (1) | condition_variable(); |
---|---|
copy [deleted] (2) | condition_variable (const condition_variable&) = delete; |
std::condition_variable 的拷貝構造函數被禁用,只提供了默認構造函數。
std::condition_variable::wait()介紹
unconditional (1) | void wait (unique_lock<mutex>& lck); |
---|---|
predicate (2) | template <class Predicate>void wait (unique_lock<mutex>& lck, Predicate pred); |
std::condition_variable提供了兩種 wait()函數。當前線程調用 wait()后將被阻塞(此時當前線程應g該獲得了鎖(mutex),不妨設獲得鎖 lck),直到另外某個線程調用 notify_*喚醒了當前線程。
在線程被阻塞時,該函數會自動調用 lck.unlock()釋放鎖,使得其他被阻塞在鎖競爭上的線程得以繼續執行。另外,一旦當前線程獲得通知(notified,通常是另外某個線程調用 notify_*喚醒了當前線程),wait()函數也是自動調用 lck.lock(),使得 lck的狀態和 wait函數被調用時相同。
在第二種情況下(即設置了 Predicate),只有當 pred條件為 false時調用 wait()才會阻塞當前線程,并且在收到其他線程的通知后只有當 pred為 true時才會被解除阻塞。因此第二種情況類似以下代碼:
while (!pred()) wait(lck);
請看下面例子:
#include <iostream> // std::cout
#include <thread> // std::thread, std::this_thread::yield
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx;
std::condition_variable cv;
int cargo = 0;
bool shipment_available()
{
return cargo != 0;
}
// 消費者線程.
void consume(int n)
{
for (int i = 0; i < n; ++i) {
std::unique_lock <std::mutex> lck(mtx);
cv.wait(lck, shipment_available);
std::cout << cargo << '\n';
cargo = 0;
}
}
int main()
{
std::thread consumer_thread(consume, 10); // 消費者線程.
// 主線程為生產者線程, 生產 10 個物品.
for (int i = 0; i < 10; ++i) {
while (shipment_available())
std::this_thread::yield();
std::unique_lock <std::mutex> lck(mtx);
cargo = i + 1;
cv.notify_one();
}
consumer_thread.join();
return 0;
}
程序執行結果如下:
concurrency ) ./ConditionVariable-wait
1
2
3
4
5
6
7
8
9
10
std::condition_variable::wait_for()介紹
unconditional (1) | template <class Rep, class Period>cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time); |
---|---|
predicate (2) | template <class Rep, class Period, class Predicate>bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep, Period>& rel_time, Predicate pred); |
與 std::condition_variable::wait()類似,不過 wait_for可以指定一個時間段,在當前線程收到通知或者指定的時間 rel_time超時之前,該線程都會處于阻塞狀態。而一旦超時或者收到了其他線程的通知,wait_for返回,剩下的處理步驟和 wait()類似。
另外,wait_for的重載版本(predicte(2))的最后一個參數 pred 表示 wait_for的預測條件,只有當 pred條件為 false時調用 wait()才會阻塞當前線程,并且在收到其他線程的通知后只有當 pred
為 true時才會被解除阻塞,因此相當于如下代碼:
return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));
請看下面的例子,下面的例子中,主線程等待 th線程輸入一個值,然后將 th線程從終端接收的值打印出來,在 th線程接受到值之前,主線程一直等待,每個一秒超時一次,并打印一個 ".":
#include <iostream> // std::cout
#include <thread> // std::thread
#include <chrono> // std::chrono::seconds
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable, std::cv_status
std::condition_variable cv;
int value;
void do_read_value()
{
std::cin >> value;
cv.notify_one();
}
int main ()
{
std::cout << "Please, enter an integer (I'll be printing dots): \n";
std::thread th(do_read_value);
std::mutex mtx;
std::unique_lock<std::mutex> lck(mtx);
while (cv.wait_for(lck,std::chrono::seconds(1)) == std::cv_status::timeout) {
std::cout << '.';
std::cout.flush();
}
std::cout << "You entered: " << value << '\n';
th.join();
return 0;
}
std::condition_variable::wait_until 介紹
unconditional (1) | template <class Clock, class Duration>cv_status wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time); |
---|---|
predicate (2) | template <class Clock, class Duration, class Predicate>bool wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time,Predicate pred); |
與 std::condition_variable::wait_for 類似,但是 wait_until 可以指定一個時間點,在當前線程收到通知或者指定的時間點 abs_time 超時之前,該線程都會處于阻塞狀態。而一旦超時或者收到了其他線程的通知,wait_until 返回,剩下的處理步驟和 wait_until() 類似。
另外,wait_until 的重載版本(predicte(2))的最后一個參數 pred 表示 wait_until 的預測條件,只有當 pred 條件為 false 時調用 wait() 才會阻塞當前線程,并且在收到其他線程的通知后只有當 pred 為 true 時才會被解除阻塞,因此相當于如下代碼:
while (!pred())
if ( wait_until(lck,abs_time) == cv_status::timeout)
return pred();
return true;
std::condition_variable::notify_one() 介紹
喚醒某個等待(wait)線程。如果當前沒有等待線程,則該函數什么也不做,如果同時存在多個等待線程,則喚醒某個線程是不確定的(unspecified)。
請看下例:
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx;
std::condition_variable cv;
int cargo = 0; // shared value by producers and consumers
void consumer()
{
std::unique_lock < std::mutex > lck(mtx);
while (cargo == 0)
cv.wait(lck);
std::cout << cargo << '\n';
cargo = 0;
}
void producer(int id)
{
std::unique_lock < std::mutex > lck(mtx);
cargo = id;
cv.notify_one();
}
int main()
{
std::thread consumers[10], producers[10];
// spawn 10 consumers and 10 producers:
for (int i = 0; i < 10; ++i) {
consumers[i] = std::thread(consumer);
producers[i] = std::thread(producer, i + 1);
}
// join them back:
for (int i = 0; i < 10; ++i) {
producers[i].join();
consumers[i].join();
}
return 0;
}
std::condition_variable::notify_all() 介紹
喚醒所有的等待(wait)線程。如果當前沒有等待線程,則該函數什么也不做。請看下面的例子:
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
std::mutex mtx; // 全局互斥鎖.
std::condition_variable cv; // 全局條件變量.
bool ready = false; // 全局標志位.
void do_print_id(int id)
{
std::unique_lock <std::mutex> lck(mtx);
while (!ready) // 如果標志位不為 true, 則等待...
cv.wait(lck); // 當前線程被阻塞, 當全局標志位變為 true 之后,
// 線程被喚醒, 繼續往下執行打印線程編號id.
std::cout << "thread " << id << '\n';
}
void go()
{
std::unique_lock <std::mutex> lck(mtx);
ready = true; // 設置全局標志位為 true.
cv.notify_all(); // 喚醒所有線程.
}
int main()
{
std::thread threads[10];
// spawn 10 threads:
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(do_print_id, i);
std::cout << "10 threads ready to race...\n";
go(); // go!
for (auto & th:threads)
th.join();
return 0;
}
std::condition_variable_any介紹
與 std::condition_variable類似,只不過 std::condition_variable_any的 wait函數可以接受任何 lockable參數,而 std::condition_variable只能接受 std::unique_lock<std::mutex>類型的參數,除此以外,和 std::condition_variable幾乎完全一樣。
std::cv_status枚舉類型介紹
cv_status::no_timeout | wait_for 或者 wait_until 沒有超時,即在規定的時間段內線程收到了通知。 |
---|---|
cv_status::timeout | wait_for 或者 wait_until 超時。 |
參考資料
【c++11FAQ】互斥
【c++11FAQ】std::future和std::promise
【c++11FAQ】async()
【c++11FAQ】線程(thread)
C++11 多線程——KingsLanding
第五章 條件變量與線程同步
5.2 條件變量詳解
4.3 鎖類型詳解