1. 等待線程完成
若不等待線程完成,我們就需要確保該線程訪問的數據都是有效的,直到該線程完成爲止。比如如下代碼,線程函數持有局部變量的指針或引用,當函數退出時,線程尚未執行完成。
#include <thread>
#include <iostream>
// 線程持有局部變量的指針
struct func
{
int *i;
func(int *i_) : i(i_){
}
void operator()()
{
for (unsigned j = 0; j < 100000; ++j)
{
*i = j; // 訪問非法地址
}
}
};
// 不等待線程執行完成就退出
void oops()
{
int some_local_state = 0;
func my_func(&some_local_state);
std::thread my_thread(my_func);
my_thread.detach();
}
int _tmain(int argc, _TCHAR* argv[])
{
oops();
return 0;
}
要避免這種情況,需要使用std::thread實例的join()來替換my_thread.detach()的調用,這樣就可以保證在函數退出前,線程已經結束。對一個給定的線程,只能調用一次join(),一旦調用了join(),此std::thread對象不再是可連接的,如果調用其的joinable()將返回false。
2. 在異常環境下的等待
我們需要在線程對象被銷燬前調用join或detach方法,如果要detach,通常在線程啓動後就立即調用detach方法。如果打算等待該線程,就需要仔細的選擇在哪個位置調用join。如果在線程開始之後,調用join之前發生了異常,則可能跳過對join的調用。
爲了避免應用程序在引發異常的時候被終止,你需要異常時也調用join。
void do_something_in_current_thread()
{
throw("error");
}
// 不等待線程執行完成就退出
void oops()
{
int some_local_state = 0;
func my_func(&some_local_state);
std::thread my_thread(my_func);
try
{
do_something_in_current_thread();
}
catch (const char *err_msg)
{
my_thread.join();
throw;
}
my_thread.join();
}
try/catch塊可以確保無論函數時正常退出還是異常退出,都調用了線程的join方法,但是try/catch塊看起來很囉嗦,也容易導致作用於混亂,更簡單的辦法是使用RAII-Resource Acquisition Is Initialization並提供一個類,在析構函數中調用join():
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_) :
t(t_)
{
}
// 析構函數中檢查線程是否還未被join,若沒有,則調用
~thread_guard()
{
if (t.joinable())
{
t.join();
}
}
// 將拷貝後賦值運算符標記爲=delete以避免編譯器自動生成,複製或賦值這樣一個對象可能很危險,因爲它可能比它要結合的線程的作用域存在得更久。
thread_guard(thread_guard const&) = delete;
thread_guard& operator=(thread_guard const&) = delete;
}