一旦開始了線程,需要顯示決定要等待線程函數完成或分離它自行完成。如果detach()線程不等待,你要確保通過線程訪問的數據是有效的,直至該線程完成爲止,例如線程函數持有局部變量的指針或引用,且當主函數退出的時候線程未完成,就會出錯,線程函數就會訪問一個已被銷燬的變量,解決方法是數據私有化。join()背後的含義有兩層,一是等待子線程執行完畢,避免主線程先完成,從而導致子線程終止,二是join()會清理與子線程相關聯的存儲器,這樣std::thread對象不再與子線程相關聯,這就意味着你只能對一個給定線程調用一次join(),一旦調用了join(),此std::thread對象不再是可連接的,並且joinable()將返回false。
struct func
{
int &i;
func(int& i_):i(i_){}
void operator()
{
for(unsigned int j=0;j<1000;j++)
do_something(i); //可能會訪問懸空引用
}
};
void main()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); //不等待線程完成,可能新的線程仍在運行
}
如果打算等待該線程,就需要仔細選擇在代碼那個位置調用join(),如果在線程開始調用後,join()之前引發了異常,對join()的調用就會跳過。爲了避免程序在引發異常的時候被終止,還需要在存在異常時調用join()。
struct func;
void main()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
try
{
do_somthing_in_current_thread();
}
catch(...)
{
t.join();
throw;
}
t.join();
}
使用try/catch塊很羅嗦,而且容易將作用域弄亂,所以並不是一個理想方案。如果確保線程必須在函數退出前完成是很重要的,無論是因爲它具有對其他局部變量的引用還是任何其他原因,那麼確保這是所有可能退出路徑的情況很重要,無論正常還是異常,希望提供一個簡單明瞭的機制。這種做法之一是使用標準的資源獲取初始化,並提供一個類,在它析構函數中進行join()。
class thread_guard
{
std::thread t;
public:
explicit thread_guard(std::thread &t_):t(t_){}
~thread_guard()
{
if(t.joinable()) //1
{
t.join(); //2
}
}
thread_guard(thread_guard const&)=delete; //3
thread_guard& operator=(thread_guard const&)=delete;
}
struct func;
void main()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
thread_guard g(t);
do_something_in_current_thread(); //4
}
在主線程執行到末尾4時,局部變量會按照構造函數的逆序銷燬,因此g首先被銷燬,並且析構函數2中線程被結合,即便4中引發異常的情況下也會發生。拷貝構造函數和拷貝賦值運算法被禁止,以確保它們不會被編譯器自動生成,複製或賦值這樣一個對象時比較危險的,因爲它們可能比它要結合的線程的作用域存在的更久。