C++11異步編程(std::async, std::future, std::packaged_task, std::promise)

  • 2020 年 2 月 15 日
  • 筆記

       std::async是一個函數模板,會啟動一個異步任務,最終返回一個std::future對象。在之前我們都是通過thread去創建一個子線程,但是如果我們要得到這個子線程所返回的結果,那麼可能就需要用全局變量或者引用的方法來得到結果,這樣或多或少都會不太方便,那麼async這個函數就可以將得到的結果保存在future中,然後通過future來獲取想要得到的結果。async比起thread來說可以對線程的創建又有了更好的控制,比如可以延遲創建。下面先介紹一下std::future, std::packaged_task, std::promise。

std::future

       std::future是一個類模板,提供了一個訪問異步操作的結果的機制。我們可以通過future_status去查詢future的三種狀態,分別是deferred(還未執行),ready(已經完成),timeout(執行超時),所以我們可以通過這個去查詢異步操作的狀態。future提供了一些函數比如get(),wait(),wait_for(),一般用get()來獲取future所得到的結果,如果異步操作還沒有結束,那麼會在此等待異步操作的結束,並獲取返回的結果。wait()只是在此等待異步操作的結束,並不能獲得返回結果。wait_for()超時等待返回結果。

// future<獲取的結果類型> 變量名  // async(函數名, 參數)  std::future<int> fu = std::async(fun, 1);  std::cout << fu.get() << std::endl;

std::packaged_task

       std::packaged_task是一個類模板,顧名思義是用來打包的,將一個可調用對象封裝起來,然後可以將其的返回值傳給future。std::packaged_task<函數返回類型(參數類型)> 變量名(函數名)。下面展示一下std::packaged_task()的簡單用法,也可以將函數換成lambda表達式。

#include <iostream>  #include <future>  #include <thread>    int fun(int x) {  	x++;  	x *= 10;  	std::cout << std::this_thread::get_id() << std::endl;  	std::this_thread::sleep_for(std::chrono::seconds(5));  	return x;  }      int main()  {  	std::packaged_task<int(int)> pt(fun);         // 將函數打包起來  	std::future<int> fu = pt.get_future();        // 並將結果返回給future  	std::thread t(std::ref(pt), 1);  	std::cout << fu.get() << std::endl;  	std::cout << std::this_thread::get_id() << std::endl;  	t.join();  	return 0;  }

std::promise

 std::promise是一個類模板,它的作用是在不同的線程中實現數據的同步,與future結合使用,也間接實現了future在不同線程間的同步。

#include <iostream>  #include <future>  #include <thread>    int fun(int x, std::promise<int>& p) {  	x++;  	x *= 10;  	p.set_value(x);  	std::cout << std::this_thread::get_id() << std::endl;  	return x;  }      int main()  {  	std::promise<int> p;  	std::future<int> fu = p.get_future();        // 並將結果返回給future  	std::thread t(fun, 1, std::ref(p));  	std::cout << fu.get() << std::endl;          // 當promise還沒有值的時候在此等待  	std::cout << std::this_thread::get_id() << std::endl;  	t.join();  	return 0;  }

       promise還有一個函數是set_value_at_thread_exit()這個翻譯一下就可以直到它的作用是當在這個線程執行結束的時候才會將future的狀態設置為ready,而set_value()則直接將future的狀態設置為ready。需要注意的是在使用的過程中不能多次set_value(),也不能多次get_future()和多次get(),因為一個promise對象只能和一個對象相關聯,否則就會拋出異常。

std::async

       其實這個函數是對上面的對象的一個整合,async先將可調用對象封裝起來,然後將其運行結果返回到promise中,這個過程就是一個面向future的一個過程,最終通過future.get()來得到結果。它的實現方法有兩種,一種是std::launch::async,這個是直接創建線程,另一種是std::launch::deferred,這個是延遲創建線程(當遇到future.get或者future.wait的時候才會創建線程),這兩個參數是std::async的第一個參數,如果沒有使用這個兩個參數,也就是第一個參數為空的話,那麼第一個參數默認為std::launch::async | std::launch::deferred,這個就不可控了,由操作系統根據當時的運行環境來確定是當前創建線程還是延遲創建線程。那麼std::async的第二個參數就是可調用對象的名稱,第三個參數就是可調用對象的參數。

#include <iostream>  #include <future>  #include <thread>    int fun(int x) {  	x++;  	x *= 10;  	std::cout << std::this_thread::get_id() << std::endl;  	return x;  }      int main()  {      // std::launch::deferred 當執行到fu.get才開始創建線程  	std::future<int> fu = std::async(std::launch::deferred, fun, 1);  	std::cout << fu.get() << std::endl;  	std::cout << std::this_thread::get_id() << std::endl;  	return 0;  }