天天看點

C++多線程:package_task異步調用任何目标執行操作

文章目錄

  • ​​描述​​
  • ​​函數成員及使用​​
  • ​​總結​​

我們上一篇描述關于C++多線程中的異步操作相關庫(

​async​

​和

​promise​

​),本節将分享c++标準庫中最後一個多線程異步操作庫

​package_task​

​的學習筆記。

描述

  • 頭檔案 ​

    ​<future>​

  • 聲明方式: ​

    ​template< class R, class ...Args > class packaged_task<R(Args...)>;​

  • 簡介

    ​package_task​

    ​标準類模版包裝了任何可調用的目标,其中包括函數,​

    ​std::bind​

    ​表達式,​

    ​lamda​

    ​表達式或者其他函數對象。并支援​

    ​package_task​

    ​對象調用者的異步調用。調用之後的傳回值或者産生的異常能夠被存儲在能夠被​

    ​std::future​

    ​對象通路的共享狀态中。

    綜上描述,我們很明顯能夠體會到該模版類提供的功能和​

    ​promise​

    ​類非常接近,但是​

    ​promise​

    ​類沒有辦法初始化所有可調用的對象,promise類僅提供共享狀态的多種通路機制并提供線程之間變量的共享機制。

函數成員及使用

  • 構造函數

    ​packaged_task() noexcept;​

    ​構造無任務且無共享狀态的​

    ​package_task​

    ​對象

    ​template <class F> explicit packaged_task( F&& f )​

    ​ 構造擁有共享狀态和任務副本的 std::packaged_task 對象,

    ​packaged_task( const packaged_task& ) = delete;​

    ​複制構造函數被删除, std::packaged_task 僅可移動

    ​packaged_task( packaged_task&& rhs ) noexcept;​

    ​ rhs 之前所占有的共享狀态和任務構造 std::packaged_task ,令 rhs 留在無共享狀态且擁有被移動後的任務的狀态

    檢視如下代碼:

#include <future>
#include <iostream>
#include <thread>
 
int fib(int n)
{
    if (n < 3) return 1;
    else 
    {
        std::cout << "fib  result " << (n-1)+(n-2) << std::endl;
        return (n-1) + (n-2);
    }
}
 
int main()
{
    std::packaged_task<int(int)> fib_task(&fib); 
 
    std::cout << "starting task\n";
    //此時已經将package_task調用對象的執行傳回值轉交給future對象,是以後續
    //的線程執行由惰性指派來觸發。即當future的對象的共享狀态嘗試擷取線程執行
    //結果的時候才進行線程執行,并傳回結果。類似std::async的policy:std::launch::deferred
    auto result = fib_task.get_future(); 
    std::thread t(std::move(fib_task), 40);
 
    std::cout << "waiting for task to finish...\n";
    std::cout << result.get() << '\n';
 
    std::cout << "task complete\n";
    t.join();
}      

輸出如下:

starting task
waiting for task to finish...
fib  result 77
77
task complete      
  • 析構函數​

    ​~packaged_task()​

    ​ 抛棄共享狀态并銷毀存儲的任務對象,同 std::promise::~promise ,若在令共享狀态就緒前抛棄它,則存儲以 std::future_errc::broken_promise 為 error_code 的 std::future_error 異常
  • 指派運算符

    ​​

    ​packaged_task& operator=( const packaged_task& ) = delete​

    ​ 複制指派運算符被删除, std::packaged_task 僅可移動
  • ​std::packaged_task<R(Args...)>::get_future​

    ​​傳回與 *this 共享同一共享狀态的 future

    同​​

    ​promise​

    ​​類一樣,get_future 隻能對每個 packaged_task 調用一次

    ​​

    ​get_future​

    ​成員出現異常的情況如下:
  • 已認證調用 get_future 取得共享狀态。設定 error_category 為 future_already_retrieved
  • *this 無共享狀态。設定 error_category 為 no_state
  • ​std::packaged_task<R(Args...)>::make_ready_at_thread_exit​

    ​​成員函數

    以轉發的 args 為參數調用存儲的任務。任務傳回值或任何抛出的異常被存儲于 *this 的共享狀态。

    僅在目前線程退出,并銷毀所有線程局域存儲期對象後,才令共享狀态就緒

    代碼如下

#include <future>
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <utility>
 
void worker(std::future<void>& output)
{
    std::packaged_task<void(bool&)> my_task{ [](bool& done) { done=true; } };
 
    auto result = my_task.get_future();
 
    bool done = false;
 
    //根據列印已經可以看到,此時已經執行了package_task的線程内容
    //但是共享狀态到函數作用域結束之前并未就緒
    my_task.make_ready_at_thread_exit(done); // 立即執行任務
 
    std::cout << "worker: done = " << std::boolalpha << done << std::endl;
 
    auto status = result.wait_for(std::chrono::seconds(0));
    if (status == std::future_status::timeout)
        std::cout << "worker: result is not ready yet" << std::endl;
 
    output = std::move(result);
}
 
 
int main()
{
    std::future<void> result;
 
    std::thread{worker, std::ref(result)}.join();
 
    //等到線程函數傳回結果,且future對象就緒之後,可以看到列印狀态變為就緒
    auto status = result.wait_for(std::chrono::seconds(0));
    if (status == std::future_status::ready)
        std::cout << "main: result is ready" << std::endl;
}      

總結

綜上對​

​package_task​

​​的描述,我們可以看到package_task模版類就像​

​promise​

​​一樣可以被異步調用,并且将調用對象執行的結果封裝在​

​future​

​​的共享狀态中,來讓調用者擷取。同時​

​package_task​

​的調用者擷取調用對象的執行結果過程就像async的惰性求值政策,當調用者想要擷取調用對象的執行結果時才開始執行調用對象。