【C/C++】多線程編程

Part1.【 thread 】(#include <thread>)ios

C++中的多線程,常經過thread類來定義一個thread對象(子線程)來實現。promise

thread t1 (func, arg1, arg2...);多線程

其中func能夠是一個函數名,或者函數對象;後邊跟這個對象的參數;併發

 

在定義一個子線程之後,要肯定他是join()或者detach()。async

  * t1.join():表示當前線程將在此處等待t1執行完相應操做後繼續執行下面的程序(已經在運行狀態的程序部分不會中止)。ide

  * t1.detach(): 表示當前程序將不會等待以及管理t1子程序的運行。函數

  * 一個子線程只能被join()或者detach()一次;一個子線程被detach後不能夠再被join性能

  *必須規定子線程是join或者detach,不然程序會終止(terminate)this

 

來看一個簡單的代碼示例:spa

#include <iostream>
#include <thread>

using namespace std;

void func() {
    for(int i = 0; i < 10; ++i) {
        cout << "From sub thread" << i << endl;
    }
}

int main() {
    thread t1(func);
    for(int j = 0; j < 10; ++j) {
        cout << "From main thread" << j << endl;
    }
    t1.join();
}

***************************************************************************************************************************************

 

Part2.【 mutex/locker/condition 】(#include <mutex>; #include <conditional_variable>)

當多個線程同時使用或操做相同資源是,常會形成資源的混亂,例如兩個線程同時在同一個隊列或文件中進行寫操做。

這種狀況下咱們能夠用mutex類來生成一個對象,用來確保同一資源在同一時間內只被一個線程訪問

具體原理是,在使用某一資源時,若是前邊有某個mutex對象mu.lock()的操做,則需先判斷mu是否處於unlock的狀態,若是沒有的話說明被mu保護的該資源在被其餘線程使用,則須要等待。

此外,因爲一些特殊情況,例如異常,mu沒有執行unlock, 會使被mutex對象保護的資源一直處於不能訪問狀態,咱們常常把mutex與一些locker類結合使用,當locker對象生命週期結束時,不管經過任何方式結束,mutex對象都會被自動析構,資源即可釋放。

經常使用的locker有:locker_guard和unique_locker。前一種效率較高,後一種功能較多更靈活,可控制locker的狀態,也可與conditional_varialble結合使用。

 

* 死鎖現象:

  使用mutex和locker時要注意避免死鎖現象,例如:線程A擁有mu1保護的資源,在等待mu2保護的資源;而線程B擁有mu2保護的資源,在等待mu1保護的資源

  避免的方法有:

  1. 一個mutex對象儘可能只對應一個資源;

  2. 若不得不安排多個mu對應一個資源,保持這些mu對象的順序一致;最好使用std::lock(),包含一些避免死鎖的機制

  3. 不要在有對象被mutex保護時去call一個用戶function/未知的function;

 

* conditional_variable: (線程間交互)

在一些狀況下,兩個線程須要交互,其中一個線程須要等待另外一個線程執行完一些操做才應該執行。

例如線程A負責往隊列裏放東西,線程B負責從隊尾拿東西;若是A中push的操做尚未完成,B會一直查看隊列中是否有元素,會致使性能的損耗。

這時會使用conditional_variable來創建一個A與B交流的方式。

好比下面一個列子:

#include <iostream>
#include <thread>
#include <mutex>
#include <deque>
#include <condition_variable>

using namespace std;

deque<int> dq;
mutex mu;
condition_variable cond;

void func1() {
    int data = 1;
    while(data <= 3) {
        unique_lock<mutex> locker(mu);
        dq.push_front(data++);
        locker.unlock();
        cond.notify_one();   //通知線程t2
        this_thread::sleep_for(chrono::seconds(1));
    }
}

void func2() {
    int data = 0;
    while(data != 3) {
        unique_lock<mutex> locker(mu);
        cond.wait(locker, [](){ return !dq.empty();});
        
        //當得到資源訪問權時cond會先讓出使用權,即將locker中mutex對象處於unlock狀態
        //因此被其保護的資源dq能夠先被其餘線程使用,直到接收到cond的notify,
        //纔會再次把locker處於lock的狀態
       
        data = dq.back();
        dq.pop_back();
        cout << data << endl;
        locker.unlock();
    }
}

int main() {
    thread t1(func1);
    thread t2(func2);
    t1.join();
    t2.join();
}
                    

t1線程向dq中放入元素,t2從dq中取出元素;

輸出結果爲:

1

2

3

*********************************************************************************************************************************** 

Part3. 【 future/promise/async 】(#include <future> ) 

有時不一樣線程或程序的不一樣地方之間通訊會有比較複雜的狀況,

Case1. 程序塊A須要程序塊B執行的函數的返回值。但程序塊A不能控制程序塊B何時結束,則可經過future對象(能夠看作一個channel)來獲取到未來某時刻B響應函數的返回值。

Eg. future<int> fu = async(myfunc, args...);

  int x = fu.get()   //可得到函數myfunc的返回值

* async函數可理解爲一個會併發進行的操做,與thread不一樣的是能夠指定async中的操做是與當前線程同時進行仍是須要時再觸發,

* async函數返回一個future對象

同一個future對象只可使用一次get()

 

Case2. 程序塊A須要程序塊B指定一些參數,可是在定義A時B還未設置該參數,則可傳入一個future對象,並將其值等於一個promise對象,而後經過promise對象以後再設置具體的值。

Eg. future<int> fu;

  promise<int> pr;

  async(myfunc, ref(fu));...

  pr.set_value(...);

要注意的是promise對象使用後,必定要設置相應的值,否則會報錯

 

來看一個完整示例:

#include <iostream>
#include <thread>
#include <future>
#include <utility>

using namespace std;

int myPow(future<pair<int,int>>& fp) {
    auto p = fp.get();
    int n = p.first, x = p.second;
    int res = 1;
    for(int i = 1; i <= x; ++i)
        res *= n;
    return res;
}

int main() {
    promise<pair<int, int>> pr;
    future<pair<int, int>> fp = pr.get_future();
    future<int> fu = async(myPow, std::ref(fp));
    pr.set_value(make_pair(2,4));
    int res = fu.get();
    cout << res << endl;
」

myPow來計算一個數字x的n次方;經過future對象fp, 把參數x和n傳入。而fp是經過promise對象pr來設置的。

此外myPow計算的結果又經過future對象fu傳回。

 

運行結果爲:16

************************************************************************************************************

Part4【 packaged_task 】

另一個callable 類型,基本定義爲:

packaged_task<T1(T2...)> pt (myFunc);

其中T1是函數myFunc返回類型,T2..是myFunc的各函數類型;運行函數時應該使用:pt(arg1, arg2...);

一個packaged_task類型對象能夠經過調用get_future()返回一個future對象

 

* 返回future類型的途徑

1. async(),函數直接返回類型;

2. promise對象,調用get_future();

3. packaged_task對象,調用get_future();

 

一個代碼示例:

#include <iostream>
#include <thread>
#include <future>
#include <queue>

using namespace std;

int factorial(int n) {
    int res = 1;
    for(int i = n; i >= 1; --i)
        res *= n;
    return n;
}

queue<packaged_task<int()> q;
mutex mu;
conditional_variable cond;

void thread_1() {
    unique_lock<mutex> locker(mu);
    cond.wait(locker, [](){return !q.empty();});
    packaged_task<int()> t = move(q.front());
    q.pop();
    t();
}

int main(){
    thread t1(thread_1);
    packaged_task<int()> t(bind(factorial,6));
    unique_lock<mutex> locker(mu);
    q.push(t);
    cond.notify_one();
    t1.join();
    int x = t.get_future().get();
    cout << x << endl;
}

 

主線程中:

  定義要執行thread_1的子線程t1,

  定義要執行factorial的packaged_task t,並把他push到任務隊列中,

  通知t1:

t1中:

  t1從任務隊列中拿出t並執行factorial,執行結果被保存在t中(future channel中)

主線程中:

  t1結束後,從t中拿到結果

運行結果爲:16

 

**以上內容參考相關視頻: https://www.bilibili.com/video/BV1ut411y7u5**

相關文章
相關標籤/搜索