C++11 標準庫提供std::tread類用於多線程編程。
線程的創建和阻塞
線程的三種創建方式
- 函數名或函數指針創建線程
- 函數對象(仿函數)創建線程
- lambda表達式創建線程。
阻塞主線程:join()和detach()函數
兩者區別:子線程對象調用join()
函數,主線程會被阻塞,直到子線程執行完成,主線程才能接着執行。子線程對象調用detach()
函數後,執行的子線程從線程對象中分離,並不會阻塞主線程的運行。
thread類的線程對象在被銷燬前必須要調用join()
函數或者detach()
函數,此時線程對象沒有關聯的線程,會處於unjoinable
狀態,可以正常調用析構函數。如果當前線程對象有關聯的線程,即處於joinable
狀態,此時調用對象的析構函數會出現運行異常terminate called without an active exception
。
PS:std::tread類的對象不能夠進行拷貝,只能進行對象資源的轉移。當使用std::move()將執行線程從當前對象轉移到其他對象後,當前對象也會處於unjoinable
狀態。
#include<thread>
#include<iostream>
using namespace std;
void thread_function(int n) {
for(int i = 0; i < 100; i++){
cout << "thread function " << n << " excuting" << endl;
}
}
void (*f)(int n); // 聲明函數指針
class DisplayThread {
public:
void operator() (int n) {
for (int i = 0; i < 100; i++){
cout << "Display Thread " << n <<" Excecuting" << endl;
}
}
};
int main(){
/* 線程的創建者(父線程)必須管理創建的線程(子線程),應該等到子線程完成其任務或者讓子線程從自己身上脫離。*/
// 線程創建
f = thread_function;
// thread threadObj(thread_function); // 用函數名創建線程。函數名同數組名一樣都是個常量,表示函數體的首地址,並不是完全意義上的函數指針。
thread threadObj(f,1); //用函數指針創建線程
DisplayThread objectFunc;
// objectFunc(); //objectFunc()調用與thread_function()函數調用形式上完全一樣
thread threadObj2(objectFunc,2); // 函數對象創建線程(函數對象是類的實例,調用操作符()被重載)
thread threadObj3([]{ //lambda表達式創建線程
for (int i = 0; i < 100; i++){
cout << "thread lambda expression excuting" << endl;
}
});
threadObj3.join(); // block線程,直到線程執行完畢,線程對象可以被銷燬(threadObj3執行完後,纔會接下去執行)
// threadObj3.detach(); // 將線程從線程對象中分離,線程對象可以被銷燬(被分離的線程仍與其它的線程並行執行)
for (int i = 0; i < 20; i++){
cout << "Display from MainThread" << endl;
}
thread threadObj4; // 默認初始化對象,還未傳入回調函數
cout << "before starting, unjoinable: " << threadObj4.joinable() << '\n';//0
threadObj4 = thread( (DisplayThread()) , 4); // 函數對象創建線程(通過DisplayThread()創建DisplayThread類的臨時對象)
cout << "after starting, joinable: " << threadObj4.joinable() << '\n'; //1
thread threadObj5 = std::move(threadObj4); // 線程對象不能拷貝,只能轉移。move()將threadObj4對象的資源轉移到threadObj5
cout << threadObj.get_id() << endl; // 2
cout << threadObj2.get_id() << endl; // 3
cout << threadObj3.get_id() << endl; // 4
cout << "after move, joinable: " << threadObj4.joinable() << '\n'; // 0
// cout << threadObj4.get_id() << endl; // threadObj4對象的線程已經被轉移,處於unjoinable狀態
cout << threadObj5.get_id() << endl; // 5
cout << this_thread::get_id() << endl; //返回當前線程id, 1
threadObj.join();
threadObj2.join();
// threadObj4.join(); // unjoinable的線程對象不能調用join()或者detach()
threadObj5.join();
cout << "after join(), unjoinable: " << threadObj4.joinable() << '\n';//0
cout << "Exit of Main function" << endl;
return 0;
}
小結:不要在沒有關聯執行線程(unjoinable狀態)的thread對象上調用join()或者detach()函數;不要忘記在關聯了執行線程(joinable狀態)的thread對象上調用join()或者detach()。
PS:如果主線程運行完了,那些被detach分離的線程也會隨着主線程的銷燬而被掛起,停止運行。
線程的互斥和同步
互斥:互斥量
同步:互斥量+條件變量
條件變量(condition_variable)的使用過程:
- 擁有條件變量的線程獲取互斥量。
unique_lock<mutex> locker(mtx);
- 循環檢查某個條件,如果條件不滿足,則阻塞當前線程直到條件滿足;如果條件滿足則向下執。
while(!firstOK){ cond.wait(locker); }
// 當條件變量的對象調用wait()函數時,它使用互斥量鎖住當前線程。
// 當前線程一直被阻塞,直到另外一個線程在同一個條件變量的對象上調用notify_one()或notify_all()函數來喚醒當前線程。
// 用while是因爲如果當前條件不滿足,線程就算被喚醒了,依然需要被繼續阻塞等待條件滿足。
- 某個線程滿足條件執行完後,調用
notify_one()
或notify_all()
喚醒一個或者所有等待的線程。
cond.notify_all();
同步實例:leetcode-按序打印
#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<functional>
using namespace std;
class Foo {
public:
int count = 0;
Foo():firstOK(false), secondOK(false) {
}
void first(function<void()> printFirst) {
unique_lock<mutex> locker(mtx); // std::unique_lock<T>爲鎖管理模板類,std::unique_lock對象獨佔mutex對象的所有權,管理mutex對象的上鎖和解鎖操作
// printFirst() outputs "first". Do not change or remove this line.
printFirst();
firstOK = true;
count++;
cond.notify_all();
}
void second(function<void()> printSecond) {
unique_lock<mutex> locker(mtx);
while(!firstOK){
cond.wait(locker);
}
// printSecond() outputs "second". Do not change or remove this line.
printSecond();
secondOK = true;
count++;
cond.notify_all();
}
void third(function<void()> printThird) {
unique_lock<mutex> locker(mtx);
while(!secondOK){
cond.wait(locker);
}
// printThird() outputs "third". Do not change or remove this line.
printThird();
count++;
cond.notify_all();
}
private:
mutex mtx;
condition_variable cond;
bool firstOK;
bool secondOK;
};
void printFirst(){
cout << "first" << endl;
}
void printSecond(){
cout << "second" << endl;
}
void printThird(){
cout << "third" << endl;
}
int main(){
// Foo* f = new Foo();
// thread t1(&Foo::first, f, printFirst);
// thread t3(&Foo::third, f, printThird);
// thread t2(&Foo::second, f, printSecond);
Foo f;
// Foo* ff = new Foo();
// std::thread類成員函數作爲線程函數,第一個參數是函數指針,第二個參數是對象指針(在類成員函數中創建線程,即this指針)
// 三個線程共用一個Foo的對象,共享互斥量、條件變量等
thread t1(&Foo::first, &f, printFirst);
thread t3(&Foo::third, &f, printThird);
// thread t3(&Foo::third, ff, printThird); // 不是共用一個對象會出現死鎖,因爲兩個對象之間的互斥量,條件變量不共享
thread t2(&Foo::second, &f, printSecond);
t1.join();
t2.join();
t3.join();
cout << f.count << endl;
// cout << ff->count << endl;
}
PS:std::thread類成員函數作爲線程函數,第一個參數是函數指針,第二個參數是對象指針(在類成員函數中創建線程,即this指針) 參考博客