1. 程式人生 > 其它 >C++11——多執行緒程式設計9

C++11——多執行緒程式設計9

這節是討論怎麼使用std::async來執行非同步task

什麼是std::async
std::async()是一個接受回撥(函式或函式物件)作為引數的函式模板,並有可能非同步執行它們

template <class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async (launch policy, Fn&& fn, Args&&... args);

std::async返回一個std::future<T>,它儲存由std::async()執行的函式物件返回的值。

函式期望的引數可以作為函式指標引數後面的引數傳遞給std::async()。

std::async中的第一個引數是啟動策略,它控制std::async的非同步行為,我們可以用三種不同的啟動策略來建立std::async
·std::launch::async
保證非同步行為,即傳遞函式將在單獨的執行緒中執行
·std::launch::deferred
當其他執行緒呼叫get()來訪問共享狀態時,將呼叫非非同步行為
·std::launch::async | std::launch::deferred
預設行為。有了這個啟動策略,它可以非同步執行或不執行,這取決於系統的負載,但我們無法控制它。

如果我們不指定一個啟動策略,其行為將類似於std::launch::async | std::launch::deferred

本節我們將使用std::launch::async啟動策略

我們可以在std::async傳遞任何回撥,如:
·函式指標
·函式物件
·lambda表示式

std::async的需求
假設我們必須從資料庫和檔案系統裡裡獲取一些資料(字串),然後需要合併字串並列印。

在單執行緒中,我們這樣做:

#include <iostream>
#include <string>
#include <chrono>
#include <thread>

using namespace std::chrono;

std::string fetchDataFromDB(std::string
recvData) { //確保函式要5秒才能執行完成 std::this_thread::sleep_for(seconds(5)); //處理建立資料庫連線、獲取資料等事情 return "DB_" + recvData; } std::string fetchDataFromFile(std::string recvData) { //確保函式要5秒才能執行完成 std::this_thread::sleep_for(seconds(5)); //處理獲取檔案資料 return "File_" + recvData; } int main() { //獲取開始時間 system_clock::time_point start = system_clock::now(); //從資料庫獲取資料 std::string dbData = fetchDataFromDB("Data"); //從檔案獲取資料 std::string fileData = fetchDataFromFile("Data"); //獲取結束時間 auto end = system_clock::now(); auto diff = duration_cast<std::chrono::seconds>(end - start).count(); std::cout << "Total Time taken= " << diff << "Seconds" << std::endl; //組裝資料 std::string data = dbData + " :: " + fileData; //輸出組裝的資料 std::cout << "Data = " << data << std::endl; return 0; }

由於函式fetchDataFromDB()和fetchDataFromFile()各自在單獨的執行緒中執行5秒,所以,總共耗時10秒。
既然從資料庫和檔案系統中獲取資料是獨立的並且都要耗時,那我們可以並行地執行他們。

一種方式是建立一個新的執行緒傳遞一個promise作為執行緒函式的引數,並在呼叫執行緒中從關聯的std::future物件獲取資料
另一種方式就是使用std::async

使用函式指標呼叫std::async作為回撥
修改上面的程式碼,並使用std::async非同步呼叫fetchDataFromDB()

std::future<std::string>resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
 
std::string dbData = resultDromDB.get()

std::async()做如下的事情
·自動建立一個執行緒(或從內部執行緒池中挑選)和一個promise物件。
·然後將std::promise物件傳遞給執行緒函式,並返回相關的std::future物件
·當我們傳遞引數的函式退出時,它的值將被設定在這個promise物件中,所以最終的返回值將在std::future物件中可用

現在改變上面的例子,使用std::async非同步地從資料庫中獲取資料

#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>

using namespace std::chrono;

std::string fetchDataFromDB(std::string recvData) {
    //確保函式要5秒才能執行完成
    std::this_thread::sleep_for(seconds(5));

    //處理建立資料庫連線、獲取資料等事情
    return "DB_" + recvData;
}

std::string fetchDataFromFile(std::string recvData) {
    //確保函式要5秒才能執行完成
    std::this_thread::sleep_for(seconds(5));

    //處理獲取檔案資料
    return "File_" + recvData;
}

int main() {
    //獲取開始時間
    system_clock::time_point start = system_clock::now();

    std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");

    //從檔案獲取資料
    std::string fileData = fetchDataFromFile("Data");

    //從DB獲取資料
    //資料在future<std::string>物件中可獲取之前,將一直阻塞
    std::string dbData = resultFromDB.get();

    //獲取結束時間
    auto end = system_clock::now();

    auto diff = duration_cast<std::chrono::seconds>(end - start).count();
    std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;

    //組裝資料
    std::string data = dbData + " :: " + fileData;

    //輸出組裝的資料
    std::cout << "Data = " << data << std::endl;

    return 0;
}

用Function物件作為回撥呼叫std::async

/*
* Function Object
*/
struct DataFetcher {
  std::string operator ()(std::string recvdData) {
    //確保函式要5秒才能執行完成
    std::this_thread::sleep_for(seconds(5));
    //處理獲取檔案資料
    return "File_" + recvdData;
 
  }
};
 
//用函式物件呼叫std::async
std::future<std::string> fileResult = std::async(DataFetcher(), "Data"); 

用lambda函式作為回撥呼叫std::async

std::future<std::string> resultFromDB = std::async([](std::string recvdData) {
 
  std::this_thread::sleep_for(seconds(5));
  //處理建立資料庫連線、獲取資料等事情
  return "DB_" + recvdData;
 
}, "Data");