2 線程啓動、結束,創建線程方法,join、detach
文章目錄
2.1 範例演示線程運行的開始和結束
每個程序至少有一個線程:執行main()函數的線程(爲主線程),其餘線程有其各自的入口函數。線程與主線程(線程以main()爲入口函數的線程)
同時運行。如同main()函數執行完會退出一樣,當線程執行完入口函數後,線程也會退出。在爲一個線程創建了一個std::thread
對象後,需要等待這個線程結束;不過,線程需要先進行啓動。下面就來啓動線程。
-
包含一個頭文件thread
#include <thread>
-
初始函數要寫
void func() { cout << "子線程開始" << endl; cout << "do something..." << endl; cout << "子線程結束" << endl; }
-
main中寫開始代碼
int main() { thread t(func); return 0; }
代碼2-1-1
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子線程開始" << endl;
cout << "do something..." << endl;
cout << "子線程結束" << endl;
}
int main() {
thread t(func);
return 0;
}
運行結果
如果主線程執行速度快於子線程的,將會是以下結果
libc++abi.dylib: terminating
Abort trap: 6
2.1.1 居然直接報錯了,這是爲什麼呢?
主線程執行完畢,thread對象消亡時(即代碼中的t對象),如果thread是joinable的(associated應該翻譯爲可關聯的,也就是joinable的) ,析構函數會調用terminate(),terminate()會調用abort(), abort()是非正常結束進程,不進行任何清理工作,直接終止程序,其典型實現是輸出標準錯誤流(即cerr使用的錯誤流)。如果thread被調用過join()或者detach(),那它就不是joinable的,所以不會引發terminate,進程正常終止。
2.1.2 “秀兒”的解決方法
修改代碼,在main()函數中加入while(1)
代碼2-1-2
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子線程開始" << endl;
cout << "do something..." << endl;
cout << "子線程結束" << endl;
}
int main() {
thread t(func);
while(1);
return 0;
}
這樣,子線程一定會先執行完成,主線程卡在死循環裏~,讓我們看看運行結果吧~
運行結果
子線程開始
do something...
子線程結束
這下終於不會報錯,且打出了我們想要的結果,但這樣的程序肯定不是我們所想要的。
2.1.3 如何解決報錯問題,利用detach()
代碼2-1-3
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子線程開始" << endl;
cout << "do something..." << endl;
cout << "子線程結束" << endl;
}
int main() {
thread t(func);
t.detach();
return 0;
}
運行結果
有時候這樣
子線程開始
do something...
子線程結束
有時候啥也沒有
2.1.5 有時候有,有時候無這是爲什麼呢?
一旦detach()之後,與這個主線程關聯的thread對象就會失去與這個主線程的關聯,此時這個子線程就會駐留在後臺運行(主線程跟該子線程失去聯繫)這個子線程就相當於被C++運行時庫接管了,當這個子線程執行完畢後,由運行時庫負責清理該線程相關的資源。
2.1.6 怎麼才能讓程序正常運行,且輸出想要的結果?利用join()
-
有關join()的解釋
加入/匯合子線程,即阻塞主線程,讓主線程等待子線程執行完畢,然後子線程和主線程匯合,然後繼續執行以下代碼,如果主線程執行完畢,子線程沒有執行完畢,這種程序是不合格的,寫出來的程序也是不穩定的 一個書寫良好的程序,應該是主線程,等待子線程執行完畢後,自己才能最終退出。
代碼2-1-4
#include <iostream>
#include <thread>
using namespace std;
void func() {
cout << "子線程開始" << endl;
cout << "do something..." << endl;
cout << "子線程結束" << endl;
}
int main() {
thread t(func);
t.join();
return 0;
}
運行結果
子線程開始
do something...
子線程結束
2.1.7 如何判斷線程是否能夠join?利用joinable()
-
joinable()的解釋
判斷是否可以成功調用join或者detach,返回true和false,true:可以join和detach,false:不可以join和detach,且detach的線程,不可以再次join,join過的線程也不可以join。
2.2 其他創建線程的手法
2.2.1 用類,以及一個問題範例
如果用類作爲一個參數傳遞給入口函數,類必須重載operator()構造函數,即向入口函數傳遞一個factor實例。
Attention!!
有件事需要注意,當把函數對象傳入到線程構造函數中時,需要避免“最令人頭痛的語法解析”(C++’s most vexing parse)。如果你傳遞了一個臨時變量,而不是一個命名的變量;C++編譯器會將其解析爲函數聲明,而不是類型對象的定義。
thread t(A());
這裏相當與聲明瞭一個名爲A的函數,這個函數帶有一個參數(函數指針指向沒有參數並返回A對象的函數),返回一個std::thread
對象的函數,而非啓動了一個線程。
使用在前面命名函數對象的方式,或使用多組括號①,或使用新統一的初始化語法②,可以避免這個問題。
thread t((A())); // 1
thread t{A()}; // 2
代碼2-2-1
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
int &m_i;
A(int &i):m_i(i) {
cout << "A的構造函數" << endl;
}
void operator() () {
cout << "線程開始" << endl;
cout << "m_i的值爲:" << this->m_i << endl; // 產生不可預料的結果
cout << "線程結束" << endl;
}
};
int main() {
int num = 6;
A a(num);
thread t(a);
t.detach();
return 0;
}
運行結果
A的構造函數
2.2.2 爲啥能安全地傳遞對象?
雖然未打印出6的值,但我們的關注點並不是這個(線程在後臺已經執行了打印的語句,但是主線程先執行完畢,所以看不到輸出結果),而是a作爲主線程的局部變量,當主線程執行完畢,a變量釋放,按一般情況會出錯,真的會出錯嗎?
答案是:不會的~。爲了進一步說明這個現象,在類中加入了拷貝構造函數和析構函數,並打印主線程對象的地址和子線程中this的值(即對象的地址)代碼如下:
代碼2-2-2
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
int &m_i;
A(int &i):m_i(i) {
cout << "A的構造函數" << endl;
cout << "this's addr is: " << this << endl;
}
A(const A& a):m_i(a.m_i) {
cout << "A的拷貝構造函數" << " this's addr is: " << this << endl;
}
~A() {
cout << "A的析構函數" << " this's addr is: " << this << endl;
}
void operator() () {
cout << "線程開始" << endl;
cout << "m_i的值爲:" << this->m_i << " this's addr is: " << this << endl;
cout << "線程結束" << endl;
}
};
int main() {
int num = 6;
cout << "num's addr is : " << &num << endl;
A a(num);
thread t(a);
t.join(); // 方便觀察
return 0;
}
運行結果
A的構造函數
this's addr is: 0x7ffee0bb2710
a's addr is : 0x7ffee0bb2718
A的拷貝構造函數this's addr is: 0x7ffee0bb26f0
A的拷貝構造函數this's addr is: 0x7f8490402bd8
A的析構函數this's addr is: 0x7ffee0bb26f0
線程開始
m_i的值爲:6 this's addr is: 0x7f8490402bd8
線程結束
A的析構函數this's addr is: 0x7f8490402bd8
A的析構函數this's addr is: 0x7ffee0bb2710
主線程中a對象的地址爲0x7ffee45d8718
,最終執行輸出對象的地址爲0x7f8490402bd8
,**結論:由主線程傳遞給子線程的對象實際上是被是被複制到線程中去的。
2.2.3 用lambda表達式
代碼2-2-3
#include <iostream>
#include <thread>
using namespace std;
int main() {
auto annoymous_func = [] {
cout << "線程開始" << endl;
cout << "do something" << endl;
cout << "線程結束" << endl;
};
thread t(annoymous_func);
t.join();
return 0;
}
運行結果
線程開始
do something...
線程結束