C++11多線程編程(筆記)

C++11 標準庫提供std::tread類用於多線程編程。

線程的創建和阻塞

線程的三種創建方式

  1. 函數名或函數指針創建線程
  2. 函數對象(仿函數)創建線程
  3. 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)的使用過程:

  1. 擁有條件變量的線程獲取互斥量。
unique_lock<mutex> locker(mtx);
  1. 循環檢查某個條件,如果條件不滿足,則阻塞當前線程直到條件滿足;如果條件滿足則向下執。
while(!firstOK){ cond.wait(locker); } 
// 當條件變量的對象調用wait()函數時,它使用互斥量鎖住當前線程。
// 當前線程一直被阻塞,直到另外一個線程在同一個條件變量的對象上調用notify_one()或notify_all()函數來喚醒當前線程。
// 用while是因爲如果當前條件不滿足,線程就算被喚醒了,依然需要被繼續阻塞等待條件滿足。
  1. 某個線程滿足條件執行完後,調用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指針) 參考博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章