在學習C++11的std::thread時,起初非常不理解join()函數的作用以及使用場景,官方的解釋又比較晦澀難懂,總覺得get不到關鍵點。看了很多文章后加上自己的理解,才覺得有了一點眉目,下面結合場景記錄一下自己的淺見。
在簡單的程序中一般只需要一個線程就可以搞定,也就是主線程:
int main() { cout << "主線程開始運行\n"; }
現在假設我要做一個比較耗時的工作,從一個服務器下載一個視頻並進行處理,那么我的代碼會變成:
int main() { cout << "主線程開始運行\n"; download(); // 下載視頻到本地 process(); // 本地處理 }
如果我需要兩個視頻素材一起在本地進行處理,也很簡單:
int main() { cout << "主線程開始運行\n"; download1(); // 下載視頻1 download2(); // 下載視頻2 process(); // 一起處理一下 }
本身這么做完全沒有問題,可是就是有點浪費時間,如果兩個視頻能夠同時下載就好了,這時候線程就派上了用場:
void download1() { cout << "開始下載第一個視頻..." << endl; for (int i = 0; i < 100; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(50)); cout << "下載進度:" << i << endl; } cout << "第一個視頻下載完成..." << endl; } void download2() { cout << "開始下載第二個視頻..." << endl; for (int i = 0; i < 100; ++i) { std::this_thread::sleep_for(std::chrono::milliseconds(30)); cout << "下載進度:" << i << endl; } cout << "第二個視頻下載完成..." << endl; } int main() { cout << "主線程開始運行\n"; std::thread d2(download2); download1(); process(); }
主線程叫來了d2這個線程去下載第二個視頻,自己去下載第一個視頻,減輕了自己的工作量也縮短了時間,仔細看一下download2()中的sleep可以發現,兩個視頻同時下載肯定是視頻二先下載完,這樣在主線程下載完視頻一的時候視頻二已經准備好了,后面就可以一起進行處理,沒什么問題,但是萬一視頻二的下載時間比視頻一的時間長呢?當視頻一下載完了,d2還沒干完活,本地還沒有視頻二,接下來處理的時候肯定會有問題,或者說接下來不能直接進行處理,要等d2干完活后,主線程才能去處理兩個視頻。
在這種場景下就用到了join()這個函數。
先貼一下關於join()函數的解釋:
The function returns when the thread execution has completed.This synchronizes the moment this function returns with the completion of all the operations in the thread: This blocks the execution of the thread that calls this function until the function called on construction returns (if it hasn't yet).
總結理解一下就是兩個關鍵點:
- 誰調用了這個函數?調用了這個函數的線程對象,一定要等這個線程對象的方法(在構造時傳入的方法)執行完畢后(或者理解為這個線程的活干完了!),這個join()函數才能得到返回。
- 在什么線程環境下調用了這個函數?上面說了必須要等線程方法執行完畢后才能返回,那必然是阻塞調用線程的,也就是說如果一個線程對象在一個線程環境調用了這個函數,那么這個線程環境就會被阻塞,直到這個線程對象在構造時傳入的方法執行完畢后,才能繼續往下走,另外如果線程對象在調用join()函數之前,就已經做完了自己的事情(在構造時傳入的方法執行完畢),那么這個函數不會阻塞線程環境,線程環境正常執行。
接下來修改代碼並結合起來解釋一下:
1 void download1() 2 { 3 cout << "開始下載第一個視頻..." << endl; 4 for (int i = 0; i < 100; ++i) { 5 std::this_thread::sleep_for(std::chrono::milliseconds(50)); 6 cout << "第一個視頻下載進度:" << i << endl; 7 } 8 cout << "第一個視頻下載完成..." << endl; 9 } 10 11 void download2() 12 { 13 cout << "開始下載第二個視頻..." << endl; 14 for (int i = 0; i < 100; ++i) { 15 std::this_thread::sleep_for(std::chrono::milliseconds(80)); 16 cout << "第二個視頻下載進度:" << i << endl; 17 } 18 cout << "第二個視頻下載完成..." << endl; 19 } 20 void process() 21 { 22 cout << "開始處理兩個視頻" << endl; 23 } 24 25 int main() 26 { 27 cout << "主線程開始運行\n"; 28 std::thread d2(download2); 29 download1(); 30 d2.join(); 31 process(); 32 }
現在下載視頻1需要5秒,下載視頻2需要8秒,當視頻1下載完成后要等待視頻2下載完成才能一起進行處理,為了實現這個目的我們在30行只加入了一行代碼d2.join()。
在這個場景下,我們明確兩個事情:
- 誰調用了join()函數?d2這個線程對象調用了join()函數,因此必須等待d2的下載任務結束了,d2.join()函數才能得到返回。
- d2在哪個線程環境下調用了join()函數?d2是在主線程的環境下調用了join()函數,因此主線程要等待d2的線程工作做完,否則主線程將一直處於block狀態;這里不要搞混的是d2真正做的任務(下載)是在另一個線程做的,但是d2調用join()函數的動作是在主線程環境下做的。
以上是結合具體場景分析join()函數的使用方法和用途,為了便於理解有些啰嗦了,當然實際中join()應該會有很多的用法和學問,先了解一下作為入門鋪墊吧。