在學習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()函數的動作是在主線程環境下做的。
參考鏈接: https://www.cnblogs.com/adorkable/p/12722209.html

