020-C++線程間通信

《C++文章匯總》
上一篇介紹了《019-智能指針》,本文介紹多線程通信。

多線程并發:在同一時間段內交替處理多個操作,線程切換時間片是很短的(一般為毫秒級),一個時間片多數時候來不及處理完對某一資源的訪問;
線程間通信:一個任務被分割為多個線程并發處理,多個線程可能都要處理某一共享內存的數據,多個線程對同一共享內存數據的訪問需要準確有序。
同步:是指在不同進程之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先后次序來運行,這種先后次序依賴于要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。
互斥:是指散布在不同進程之間的若干程序片斷,當某個進程運行其中一個程序片段時,其它進程就不能運行它們之中的任一程序片段,只能等到該進程運行完這個程序片段后才可以運行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

// mutex1.cpp       通過互斥體lock與unlock保護共享全局變量

#include <chrono>
#include <mutex>
#include <thread>
#include <iostream> 

std::chrono::milliseconds interval(100);
 
std::mutex mutex;
int job_shared = 0; //兩個線程都能修改'job_shared',mutex將保護此變量
int job_exclusive = 0; //只有一個線程能修改'job_exclusive',不需要保護

//此線程只能修改 'job_shared'
void job_1()
{
    mutex.lock();
    std::this_thread::sleep_for(5 * interval);  //令‘job_1’持鎖等待
    ++job_shared;
    std::cout << "job_1 shared (" << job_shared << ")\n";
    mutex.unlock();
}

// 此線程能修改'job_shared'和'job_exclusive'
void job_2()
{
    while (true) {    //無限循環,直到獲得鎖并修改'job_shared'
        if (mutex.try_lock()) {     //嘗試獲得鎖成功則修改'job_shared'
            ++job_shared;
            std::cout << "job_2 shared (" << job_shared << ")\n";
            mutex.unlock();
            return;
        } else {      //嘗試獲得鎖失敗,接著修改'job_exclusive'
            ++job_exclusive;
            std::cout << "job_2 exclusive (" << job_exclusive << ")\n";
            std::this_thread::sleep_for(interval);
        }
    }
}

int main() 
{
    std::thread thread_1(job_1);
    std::thread thread_2(job_2);
 
    thread_1.join();
    thread_2.join();

    getchar();
    return 0;
}
job_2 shared (1)
job_1 shared (2)

從上面的代碼看,創建了兩個線程和兩個全局變量,其中一個全局變量job_exclusive是排他的,兩線程并不共享,不會產生數據競爭,所以不需要鎖保護。另一個全局變量job_shared是兩線程共享的,會引起數據競爭,因此需要鎖保護。線程thread_1持有互斥鎖lock的時間較長,線程thread_2為免于空閑等待,使用了嘗試鎖try_lock,如果獲得互斥鎖則操作共享變量job_shared,未獲得互斥鎖則操作排他變量job_exclusive,提高多線程效率。

lock_guard與unique_lock保護共享資源

但lock與unlock必須成對合理配合使用,使用不當可能會造成資源被永遠鎖住,甚至出現死鎖(兩個線程在釋放它們自己的lock之前彼此等待對方的lock)。是不是想起了C++另一對兒需要配合使用的對象new與delete,若使用不當可能會造成內存泄漏等嚴重問題,為此C++引入了智能指針shared_ptr與unique_ptr。智能指針借用了RAII技術(Resource Acquisition Is Initialization—使用類來封裝資源的分配和初始化,在構造函數中完成資源的分配和初始化,在析構函數中完成資源的清理,可以保證正確的初始化和資源釋放)對普通指針進行封裝,達到智能管理動態內存釋放的效果。同樣的,C++也針對lock與unlock引入了智能鎖lock_guard與unique_lock,同樣使用了RAII技術對普通鎖進行封裝,達到智能管理互斥鎖資源釋放的效果。lock_guard與unique_lock的區別如下:


圖片.png

從上面兩個支持的操作函數表對比來看,unique_lock功能豐富靈活得多。如果需要實現更復雜的鎖策略可以用unique_lock,如果只需要基本的鎖功能,優先使用更嚴格高效的lock_guard。兩種鎖的簡單概述與策略對比見下表:


圖片.png

如果將上面的普通鎖lock/unlock替換為智能鎖lock_guard,其中job_1函數代碼修改如下:
void job_1()
{
    std::lock_guard<std::mutex> lockg(mutex);    //獲取RAII智能鎖,離開作用域會自動析構解鎖
    std::this_thread::sleep_for(5 * interval);  //令‘job_1’持鎖等待
    ++job_shared;
    std::cout << "job_1 shared (" << job_shared << ")\n";
}

如果也想將job_2的嘗試鎖try_lock也使用智能鎖替代,由于lock_guard鎖策略不支持嘗試鎖,只好使用unique_lock來替代,代碼修改如下(其余代碼和程序執行結果與上面相同):

void job_2()
{
    while (true) {    //無限循環,直到獲得鎖并修改'job_shared'
        std::unique_lock<std::mutex> ulock(mutex, std::try_to_lock);        //以嘗試鎖策略創建智能鎖
        //嘗試獲得鎖成功則修改'job_shared'
        if (ulock) {
            ++job_shared;
            std::cout << "job_2 shared (" << job_shared << ")\n";
            return;
        } else {      //嘗試獲得鎖失敗,接著修改'job_exclusive'
            ++job_exclusive;
            std::cout << "job_2 exclusive (" << job_exclusive << ")\n";
            std::this_thread::sleep_for(interval);
        }
    }
}

timed_mutex與recursive_mutex提供更強大的鎖

互斥量mutex提供了普通鎖lock/unlock和智能鎖lock_guard/unique_lock,基本能滿足我們大多數對共享數據資源的保護需求。但在某些特殊情況下,我們需要更復雜的功能,比如某個線程中函數的嵌套調用可能帶來對某共享資源的嵌套鎖定需求,mutex在一個線程中卻只能鎖定一次;再比如我們想獲得一個鎖,但不想一直阻塞,只想等待特定長度的時間,mutex也沒提供可設定時間的鎖。針對這些特殊需求,< mutex >庫也提供了下面幾種功能更豐富的互斥類,它們間的區別見下表:


image

不同互斥類所支持的互斥鎖類型總結如下表:


image

繼續用前面的例子,將mutex替換為timed_mutex,將job_2的嘗試鎖tyr_lock()替換為帶時間的嘗試鎖try_lock_for(duration)。由于改變了嘗試鎖的時間,所以在真正獲得鎖之前的嘗試次數也有變化,該變化體現在嘗試鎖失敗后對排他變量job_exclusive的最終修改結果或修改次數上。更新后的代碼如下所示
#include <stdio.h>
#include <chrono>
#include <mutex>
#include <iostream>
#include <thread>
using namespace std;

std::chrono::milliseconds inteval(100);
std::timed_mutex tmutex;

int job_shared = 0;//兩個線程都能修改'job_shared',mutex將保護此變量
int job_exclusive = 0; //只有一個線程能修改'job_exclusive',不需要保護

//此線程只能修改 'job_shared'
void job_1(){
    std::lock_guard<std::timed_mutex> lockg(tmutex);//獲取RAII鎖,離開作用域會自動析構解鎖
    std::this_thread::sleep_for(5*inteval);//令‘job_1’封鎖等待
    ++job_shared;
    std::cout << "job_1 shared (" << job_shared << ")\n";
}
//此線程能修改‘job_shared’ 和 ‘job_exclusive’
void job_2(){
    while (true) {//無線循環只要能獲得鎖并修改“job_shared”
        std::unique_lock<std::timed_mutex> ulock(tmutex,std::defer_lock);//創建一個智能鎖但先不鎖定
        //嘗試獲得鎖成功則修改“job_shared”
        if (ulock.try_lock_for(3 * inteval)) {
            ++job_shared;
            std::cout << "job_2 shared (" << job_shared << ")\n";
            return;
        }else{//嘗試獲得鎖失敗,接著修改“job_exclusive”
            ++job_exclusive;
            std::cout << "job_2 exclusive (" << job_shared << ")\n";
            std::this_thread::sleep_for(inteval);
        }
        
    }
}
int main(){
    std::thread t1(job_1);
    std::thread t2(job_2);
    t1.join();
    t2.join();
    getchar();
    return 0;
}
job_2 exclusive (0)
job_1 shared (1)
job_2 shared (2)

C++線程間通信有三種方式

(1)通過條件變量進行線程間的通信
(2)通過標志位來通知線程間的通信
(3)通過std::future來進行線程間的通信

1.通過條件變量進行線程間通信

條件變量使用“通知—喚醒”模型,生產者生產出一個數據后通知消費者使用,消費者在未接到通知前處于休眠狀態節約CPU資源;當消費者收到通知后,趕緊從休眠狀態被喚醒來處理數據,使用了事件驅動模型,在保證不誤事兒的情況下盡可能減少無用功降低對資源的消耗。

I.如何使用條件變量

C++標準庫在< condition_variable >中提供了條件變量,借由它,一個線程可以喚醒一個或多個其他等待中的線程。原則上,條件變量的運作如下:

你必須同時包含< mutex >和< condition_variable >,并聲明一個mutex和一個condition_variable變量;
那個通知“條件已滿足”的線程(或多個線程之一)必須調用notify_one()或notify_all(),以便條件滿足時喚醒處于等待中的一個條件變量;
那個等待"條件被滿足"的線程必須調用wait(),可以讓線程在條件未被滿足時陷入休眠狀態,當接收到通知時被喚醒去處理相應的任務;

A.cond.notify_all()

#include <iostream>
#include <stdio.h>
#include <thread>
#include <deque>
#include <mutex>
#include <vector>
#include <condition_variable>
using namespace std;

std::mutex mtx;
std::condition_variable cv;
std::vector<int> vec;
int productNum = 5;

void Producer(){
    for (int i = 1; i <= productNum; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        while (!vec.empty()) {
            cv.wait(lock);//vec 不為空時阻塞當前線程
        }
        vec.push_back(i);
        std::cout << "Producer生產產品: " << i << std::endl;
        cv.notify_all();//釋放線程鎖
    }
}
void Consumer(){
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);//vec 為空時等待線程鎖。其他線程鎖釋放時,當前線程繼續執行
        while (vec.empty()) {
            cv.wait(lock);
        }
        int data = vec.back();
        vec.pop_back();
        std::cout << "Consumer消費產品: " << data << std::endl;
        cv.notify_all();
    }
}
int main(int argc, const char * argv[]) {
    std::thread t1(Producer);
    std::thread t2(Consumer);
    t2.join();
    t1.join();
    std::cin.get();
    return 0;
}
Producer生產產品: 1
Consumer消費產品: 1
Producer生產產品: 2
Consumer消費產品: 2
Producer生產產品: 3
Consumer消費產品: 3
Producer生產產品: 4
Consumer消費產品: 4
Producer生產產品: 5
Consumer消費產品: 5

B.cond.notify_one()

//cond_var2.cpp用條件變量解決輪詢間隔難題

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

std::deque<int> q;                      //雙端隊列標準容器全局變量
std::mutex mu;                          //互斥鎖全局變量
std::condition_variable cond;           //全局條件變量
//生產者,往隊列放入數據
void function_1() {
    int count = 10;
    while (count > 0) {
        std::unique_lock<std::mutex> locker(mu);
        q.push_front(count);            //數據入隊鎖保護
        locker.unlock();
        
        cond.notify_one();              // 向一個等待線程發出“條件已滿足”的通知
        
        std::this_thread::sleep_for(std::chrono::seconds(1));       //延時1秒
        count--;
    }
}
//消費者,從隊列提取數據
void function_2() {
    int data = 0;
    while ( data != 1) {
        std::unique_lock<std::mutex> locker(mu);
        
        while(q.empty())        //判斷隊列是否為空
            cond.wait(locker); // 解鎖互斥量并陷入休眠以等待通知被喚醒,被喚醒后加鎖以保護共享數據

        data = q.back();
        q.pop_back();           //數據出隊鎖保護
        locker.unlock();
        std::cout << "t2 got a value from t1: " << data << std::endl;
    }
}

int main() {
    std::thread t1(function_1);
    std::thread t2(function_2);
    t1.join();
    t2.join();

    getchar();
    return 0;
}
  • 在function_2中,在判斷隊列是否為空的時候,使用的是while(q.empty()),而不是if(q.empty()),這是因為wait()從阻塞到返回,不一定就是由于notify_one()函數造成的,還有可能由于系統的不確定原因喚醒(可能和條件變量的實現機制有關),這個的時機和頻率都是不確定的,被稱作偽喚醒。如果在錯誤的時候被喚醒了,執行后面的語句就會錯誤,所以需要再次判斷隊列是否為空,如果還是為空,就繼續wait()阻塞;
  • 在管理互斥鎖的時候,使用的是std::unique_lock而不是std::lock_guard,而且事實上也不能使用std::lock_guard。這需要先解釋下wait()函數所做的事情,可以看到,在wait()函數之前,使用互斥鎖保護了,如果wait的時候什么都沒做,豈不是一直持有互斥鎖?那生產者也會一直卡住,不能夠將數據放入隊列中了。所以,wait()函數會先調用互斥鎖的unlock()函數,然后再將自己睡眠,在被喚醒后,又會繼續持有鎖,保護后面的隊列操作。lock_guard沒有lock和unlock接口,而unique_lock提供了,這就是必須使用unique_lock的原因;
  • 使用細粒度鎖,盡量減小鎖的范圍,在notify_one()的時候,不需要處于互斥鎖的保護范圍內,所以在喚醒條件變量之前可以將鎖unlock()。
    還可以將cond.wait(locker)換一種寫法,wait()的第二個參數可以傳入一個函數表示檢查條件,這里使用lambda函數最為簡單,如果這個函數返回的是true,wait()函數不會阻塞會直接返回,如果這個函數返回的是false,wait()函數就會阻塞著等待喚醒,如果被偽喚醒,會繼續判斷函數返回值。代碼示例如下:
//消費者,從隊列提取數據
void function_2() {
    int data = 0;
    while ( data != 1) {
        std::unique_lock<std::mutex> locker(mu);
        
        cond.wait(locker, [](){ return !q.empty();});   //如果條件變量被喚醒,檢查隊列非空條件是否為真,為真則直接返回,為假則繼續等待
        
        data = q.back();
        q.pop_back();           //數據出隊鎖保護
        locker.unlock();
        std::cout << "t2 got a value from t1: " << data << std::endl;
    }
}

下面給出條件變量支持的操作函數表:


image

值得注意的是:
所有通知(notification)都會被自動同步化,所以并發調用notify_one()和notify_all()不會帶來麻煩;
所有等待某個條件變量(condition variable)的線程都必須使用相同的mutex,當wait()家族的某個成員被調用時該mutex必須被unique_lock鎖定,否則會發生不明確的行為;
wait()函數會執行“解鎖互斥量–>陷入休眠等待–>被通知喚醒–>再次鎖定互斥量–>檢查條件判斷式是否為真”幾個步驟,這意味著傳給wait函數的判斷式總是在鎖定情況下被調用的,可以安全的處理受互斥量保護的對象;但在"解鎖互斥量–>陷入休眠等待"過程之間產生的通知(notification)會被遺失。
線程同步保證了多個線程對共享數據的有序訪問,目前我們了解到的多線程間傳遞數據主要是通過共享數據(全局變量)實現的,全局共享變量的使用容易增加不同任務或線程間的耦合度,也增加了引入bug的風險,所以全局共享變量應盡可能少用。很多時候我們只需要傳遞某個線程或任務的執行結果,以便參與后續的運算,但我們又不想阻塞等待該線程或任務執行完畢,而是繼續執行暫時不需要該線程或任務執行結果參與的運算,當需要該線程執行結果時直接獲得,才能更充分發揮多線程并發的效率優勢。

2.使用全局變量與條件變量傳遞結果進行線程間通信

同步:就是在發出一個調用時,在沒有得到結果之前,該調用就不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由調用者主動等待這個調用的結果。
異步:調用在發出之后,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果。而是在調用發出后,被調用者通過狀態、通知來通知調用者,或通過回調函數處理這個調用。


image

如何使用異步編程

在線程庫< thread >中并沒有獲得線程執行結果的方法,通常情況下,線程調用者需要獲得線程的執行結果或執行狀態,以便后續任務的執行。那么,通過什么方式獲得被調用者的執行結果或狀態呢?

#include <stdio.h>
#include <vector>
#include <numeric>
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>

int res = 0; //保存結果的全局變量
std::mutex mu; //互斥鎖全局變量
std::condition_variable cond;//全局條件變量
void accumulate(std::vector<int>::iterator first,std::vector<int>::iterator last){
    int sum = std::accumulate(first, last, 0);//標準庫求和函數
    std::cout << sum << std::endl;
    std::unique_lock<std::mutex> locker(mu);
    std::cout << "執行了" <<  res << "次" << std::endl;
    res = sum;
    locker.unlock();
    cond.notify_one();//向一個等待線程發出條件已滿足通知
}
int main(){
    std::vector<int> numbers = {1,2,3,4,5,6};
    std::thread work_thread(accumulate,numbers.begin(),numbers.end());
    std::unique_lock<std::mutex> locker(mu);
    cond.wait(locker,[](){return res;});//如果條件變量被喚醒,檢查結果是否被改變,為真則直接返回,為假則繼續等待
    std::cout << "wait被卡住" << std::endl;
    std::cout << "result=" << res << "\n";
    locker.unlock();
    work_thread.join();
    
    getchar();
    return 0;
}
21
執行了0次
wait被卡住
result=21

從上面的代碼可以看出,雖然也實現了獲取異步任務執行結果的功能,但需要的全局變量較多,多線程間的耦合度也較高,編寫復雜程序時容易引入bug。有沒有更好的方式實現異步編程呢?C++ 11新增了一個< future >庫函數為異步編程提供了很大的便利。

3.通過std::furture來進行線程間的通信

< future >頭文件功能允許對特定提供者設置的值進行異步訪問,可能在不同的線程中。
這些提供程序(要么是promise 對象,要么是packaged_task對象,或者是對異步的調用async)與future對象共享共享狀態:提供者使共享狀態就緒的點與future對象訪問共享狀態的點同步。< future >頭文件的結構如下:


圖片.png

注意前面提到的共享狀態,多線程間傳遞的返回值或拋出的異常都是在共享狀態中交流的。我們知道多線程間并發訪問共享數據是需要保持同步的,這里的共享狀態是保證返回值或異常在線程間正確傳遞的關鍵,被調用線程可以通過改變共享狀態通知調用線程返回值或異常已寫入完畢,可以訪問或操作了。future的狀態(future_status)有以下三種:

  • deferred:異步操作還沒開始;
  • ready:異步操作已經完成;
  • timeout:異步操作超時。
    既然線程間傳遞返回值或異常是通過共享狀態進行的,就涉及到共享狀態的提供方與獲取方,只有該任務或線程擁有包含共享狀態的對象,其他任務或線程才能夠通過共享狀態的通知機制同步獲取到該人物或線程的返回值或異常。我們通常使用的< thread >創建線程并不擁有共享狀態,我們需要為該線程提供一個共享狀態,以便后續對其返回值或異常的訪問。那么,怎么為一個線程提供一個包含共享狀態的對象呢?這就需要借助std::promise< T >類模板實現了,其具體用法如下:


    image

    std::promise< T >構造時,產生一個未就緒的共享狀態(包含存儲的T值和是否就緒的狀態)。可設置T值,并讓狀態變為ready。也可以通過產生一個future對象獲取到已就緒的共享狀態中的T值。繼續使用上面的程序示例,改為使用promise傳遞結果,修改后的代碼如下:

#include <stdio.h>
#include <vector>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>
void accumulate(std::vector<int>::iterator first,
                std::vector<int>::iterator last,
                std::promise<int> accumulate_promise){
    int sum = std::accumulate(first, last, 0);
    accumulate_promise.set_value(sum);//將結果存入,并讓共享狀態變為就緒以提醒future
}
int main(){
    //演示用promise<int>在線程間傳遞結果
    std::vector<int> numbers = {1,2,3,4,5,6};
    std::promise<int> accumulate_promise;
    std::future<int> accumulate_future = accumulate_promise.get_future();
    std::thread work_thread(accumulate,numbers.begin(),numbers.end(),std::move(accumulate_promise));
    accumulate_future.wait();//等待結果
    std::cout << "result=" << accumulate_future.get() << std::endl;
    work_thread.join();
    
    getchar();
    return 0;
}
result=21

std::promise< T >對象的成員函數get_future()產生一個std::future< T >對象,代碼示例中已經展示了future對象的兩個方法:wait()與get(),下面給出更多操作函數供參考:


圖片.png

值得注意的是,std::future< T >在多個線程等待時,只有一個線程能獲取等待結果。當需要多個線程等待相同的事件的結果(即多處訪問同一個共享狀態),需要用std::shared_future< T >來替代std::future < T >,std::future< T >也提供了一個將future轉換為shared_future的方法f.share(),但轉換后原future狀態失效。這有點類似于智能指針std::unique_ptr< T >與std::shared_ptr< T >的關系,使用時需要留心。

3.1使用packaged_task與future傳遞結果

除了為一個任務或線程提供一個包含共享狀態的變量,還可以直接把共享狀態包裝進一個任務或線程中。這就需要借助std::packaged_task< Func >來實現了,其具體用法如下:


image

std::packaged_task< Func >構造時綁定一個函數對象,也產生一個未就緒的共享狀態。通過thread啟動或者仿函數形式啟動該函數對象。但是相比promise,沒有提供set_value()公用接口,而是當執行完綁定的函數對象,其執行結果返回值或所拋異常被存儲于能通過 std::future 對象訪問的共享狀態中。繼續使用上面的程序示例,改為使用packaged_task傳遞結果,修改后的代碼如下:

#include <stdio.h>
#include <vector>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>

int accumulate(std::vector<int>::iterator first,
               std::vector<int>::iterator last){
    int sum = std::accumulate(first, last, 0);
    return sum;
}
int main(){
    std::vector<int> numbers = {1,2,3,4,5,6};
    std::packaged_task<int(std::vector<int>::iterator,std::vector<int>::iterator)> accumulate_task(accumulate);
    std::future<int> accumulate_future = accumulate_task.get_future();
    std::thread work_thread(std::move(accumulate_task),numbers.begin(),numbers.end());
    accumulate_future.wait();//等待結果
    std::cout << "result=" << accumulate_future.get() << '\n';
    work_thread.join();//阻塞等待線程執行完成
    getchar();
    return 0;
}
result=21

一般不同函數間傳遞數據時,主要是借助全局變量、返回值、函數參數等來實現的。上面第一種方法使用全局變量傳遞數據,會使得不同函數間的耦合度較高,不利于模塊化編程。后面兩種方法分別通過函數參數與返回值來傳遞數據,可以降低函數間的耦合度,使編程和維護更簡單快捷。

3.2使用async傳遞結果

前面介紹的std::promise< T >與std::packaged_task< Func >已經提供了較豐富的異步編程工具,但在使用時既需要創建提供共享狀態的對象(promise與packaged_task),又需要創建訪問共享狀態的對象(future與shared_future),還是覺得使用起來不夠方便。有沒有更簡單的異步編程工具呢?future頭文件也確實封裝了更高級別的函數std::async,其具體用法如下:

std::future std::async(std::launch policy, Func, Args…)
std::async是一個函數而非類模板,其函數執行完后的返回值綁定給使用std::async的std::futrue對象(std::async其實是封裝了thread,packged_task的功能,使異步執行一個任務更為方便)。Func是要調用的可調用對象(function, member function, function object, lambda),Args是傳遞給Func的參數,std::launch policy是啟動策略,它控制std::async的異步行為,我們可以用三種不同的啟動策略來創建std::async:

std::launch::async參數 保證異步行為,即傳遞函數將在單獨的線程中執行;
std::launch::deferred參數 當其他線程調用get()/wait()來訪問共享狀態時,將調用非異步行為;
std::launch::async | std::launch::deferred參數 是默認行為(可省略)。有了這個啟動策略,它可以異步運行或不運行,這取決于系統的負載。
繼續使用上面的程序示例,改為使用std::async傳遞結果,修改后的代碼如下:

#include <stdio.h>
#include <vector>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>

int accumulate(std::vector<int>::iterator first,
               std::vector<int>::iterator last){
    int sum = std::accumulate(first,last,0);
    return sum;
}
int main(){
    std::vector<int> numbers = {1,2,3,4,5,6};
    auto accumulate_future = std::async(std::launch::async, accumulate, numbers.begin(),numbers.end());
    std::cout << "result=" << accumulate_future.get() << "\n";
    getchar();
    return 0;
}
result=21

從上面的代碼可以看出使用std::async能在很大程度上簡少編程工作量,使我們不用關注線程創建內部細節,就能方便的獲取異步執行狀態和結果,還可以指定線程創建策略。所以,我們可以使用std::async替代線程的創建,讓它成為我們做異步操作的首選。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容