C++11 並發指南四( 詳解二 std::packaged_task 介紹)


上一講《C++11 並發指南四(<future> 詳解一 std::promise 介紹)》主要介紹了 <future> 頭文件中的 std::promise 類,本文主要介紹 std::packaged_task。

std::packaged_task 包裝一個可調用的對象,並且允許異步獲取該可調用對象產生的結果,從包裝可調用對象意義上來講,std::packaged_task 與 std::function 類似,只不過 std::packaged_task 將其包裝的可調用對象的執行結果傳遞給一個 std::future 對象(該對象通常在另外一個線程中獲取 std::packaged_task 任務的執行結果)。

std::packaged_task 對象內部包含了兩個最基本元素,一、被包裝的任務(stored task),任務(task)是一個可調用的對象,如函數指針、成員函數指針或者函數對象,二、共享狀態(shared state),用於保存任務的返回值,可以通過 std::future 對象來達到異步訪問共享狀態的效果。

可以通過 std::packged_task::get_future 來獲取與共享狀態相關聯的 std::future 對象。在調用該函數之后,兩個對象共享相同的共享狀態,具體解釋如下:

  • std::packaged_task 對象是異步 Provider,它在某一時刻通過調用被包裝的任務來設置共享狀態的值。
  • std::future 對象是一個異步返回對象,通過它可以獲得共享狀態的值,當然在必要的時候需要等待共享狀態標志變為 ready.

std::packaged_task 的共享狀態的生命周期一直持續到最后一個與之相關聯的對象被釋放或者銷毀為止。下面一個小例子大致講了 std::packaged_task 的用法:

#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
    for (int i=from; i!=to; --i) {
        std::cout << i << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Finished!\n";
    return from - to;
}

int main ()
{
    std::packaged_task<int(int,int)> task(countdown); // 設置 packaged_task
    std::future<int> ret = task.get_future(); // 獲得與 packaged_task 共享狀態相關聯的 future 對象.

    std::thread th(std::move(task), 10, 0);   //創建一個新線程完成計數任務.

    int value = ret.get();                    // 等待任務完成並獲取結果.

    std::cout << "The countdown lasted for " << value << " seconds.\n";

    th.join();
    return 0;
}

執行結果為:

concurrency ) ./Packaged_Task1 
10
9
8
7
6
5
4
3
2
1
Finished!
The countdown lasted for 10 seconds.

std::packaged_task 構造函數

default (1)
packaged_task() noexcept;
initialization (2)
template <class Fn>
  explicit packaged_task (Fn&& fn);
with allocator (3)
template <class Fn, class Alloc>
  explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
copy [deleted] (4)
packaged_task (const packaged_task&) = delete;
move (5)
packaged_task (packaged_task&& x) noexcept;

std::packaged_task 構造函數共有 5 中形式,不過拷貝構造已經被禁用了。下面簡單地介紹一下上述幾種構造函數的語義:

  1. 默認構造函數,初始化一個空的共享狀態,並且該 packaged_task 對象無包裝任務。
  2. 初始化一個共享狀態,並且被包裝任務由參數 fn 指定。
  3. 帶自定義內存分配器的構造函數,與默認構造函數類似,但是使用自定義分配器來分配共享狀態。
  4. 拷貝構造函數,被禁用。
  5. 移動構造函數。

下面例子介紹了各類構造函數的用法:

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> foo; // 默認構造函數.

    // 使用 lambda 表達式初始化一個 packaged_task 對象.
    std::packaged_task<int(int)> bar([](int x){return x*2;});

    foo = std::move(bar); // move-賦值操作,也是 C++11 中的新特性.

    // 獲取與 packaged_task 共享狀態相關聯的 future 對象.
    std::future<int> ret = foo.get_future();

    std::thread(std::move(foo), 10).detach(); // 產生線程,調用被包裝的任務.

    int value = ret.get(); // 等待任務完成並獲取結果.
    std::cout << "The double of 10 is " << value << ".\n";

return 0;
}

與 std::promise 類似, std::packaged_task 也禁用了普通的賦值操作運算,只允許 move 賦值運算。

std::packaged_task::valid 介紹

檢查當前 packaged_task 是否和一個有效的共享狀態相關聯,對於由默認構造函數生成的 packaged_task 對象,該函數返回 false,除非中間進行了 move 賦值操作或者 swap 操作。

請看下例:

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// 在新線程中啟動一個 int(int) packaged_task.
std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg)
{
    if (tsk.valid()) {
        std::future<int> ret = tsk.get_future();
        std::thread (std::move(tsk),arg).detach();
        return ret;
    }
    else return std::future<int>();
}

int main ()
{
    std::packaged_task<int(int)> tsk([](int x){return x*2;});

    std::future<int> fut = launcher(tsk,25);

    std::cout << "The double of 25 is " << fut.get() << ".\n";

    return 0;
}

std::packaged_task::get_future 介紹

返回一個與 packaged_task 對象共享狀態相關的 future 對象。返回的 future 對象可以獲得由另外一個線程在該 packaged_task 對象的共享狀態上設置的某個值或者異常。

請看例子(其實前面已經講了 get_future 的例子):

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    std::packaged_task<int(int)> tsk([](int x) { return x * 3; })); // package task

    std::future<int> fut = tsk.get_future();   // 獲取 future 對象.

    std::thread(std::move(tsk), 100).detach();   // 生成新線程並調用packaged_task.

    int value = fut.get();                     // 等待任務完成, 並獲取結果.

    std::cout << "The triple of 100 is " << value << ".\n";

    return 0;
}

std::packaged_task::operator()(Args... args) 介紹

調用該 packaged_task 對象所包裝的對象(通常為函數指針,函數對象,lambda 表達式等),傳入的參數為 args. 調用該函數一般會發生兩種情況:

  • 如果成功調用 packaged_task 所包裝的對象,則返回值(如果被包裝的對象有返回值的話)被保存在 packaged_task 的共享狀態中。
  • 如果調用 packaged_task 所包裝的對象失敗,並且拋出了異常,則異常也會被保存在 packaged_task 的共享狀態中。

以上兩種情況都使共享狀態的標志變為 ready,因此其他等待該共享狀態的線程可以獲取共享狀態的值或者異常並繼續執行下去。

共享狀態的值可以通過在 future 對象(由 get_future獲得)上調用 get 來獲得。

由於被包裝的任務在 packaged_task 構造時指定,因此調用 operator() 的效果由 packaged_task 對象構造時所指定的可調用對象來決定:

  • 如果被包裝的任務是函數指針或者函數對象,調用 std::packaged_task::operator() 只是將參數傳遞給被包裝的對象。
  • 如果被包裝的任務是指向類的非靜態成員函數的指針,那么 std::packaged_task::operator() 的第一個參數應該指定為成員函數被調用的那個對象,剩余的參數作為該成員函數的參數。
  • 如果被包裝的任務是指向類的非靜態成員變量,那么 std::packaged_task::operator() 只允許單個參數。

std::packaged_task::make_ready_at_thread_exit 介紹

該函數會調用被包裝的任務,並向任務傳遞參數,類似 std::packaged_task 的 operator() 成員函數。但是與 operator() 函數不同的是,make_ready_at_thread_exit 並不會立即設置共享狀態的標志為 ready,而是在線程退出時設置共享狀態的標志。

如果與該 packaged_task 共享狀態相關聯的 future 對象在 future::get 處等待,則當前的 future::get 調用會被阻塞,直到線程退出。而一旦線程退出,future::get 調用繼續執行,或者拋出異常。

注意,該函數已經設置了 promise 共享狀態的值,如果在線程結束之前有其他設置或者修改共享狀態的值的操作,則會拋出 future_error( promise_already_satisfied )。

std::packaged_task::reset() 介紹

重置 packaged_task 的共享狀態,但是保留之前的被包裝的任務。請看例子,該例子中,packaged_task 被重用了多次:

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// a simple task:
int triple (int x) { return x*3; }

int main ()
{
    std::packaged_task<int(int)> tsk (triple); // package task


    std::future<int> fut = tsk.get_future();
    std::thread (std::move(tsk), 100).detach();
    std::cout << "The triple of 100 is " << fut.get() << ".\n";


    // re-use same task object:
    tsk.reset();
    fut = tsk.get_future();
    std::thread(std::move(tsk), 200).detach();
    std::cout << "Thre triple of 200 is " << fut.get() << ".\n";

    return 0;
}

std::packaged_task::swap() 介紹

交換 packaged_task 的共享狀態。

好了,std::packaged_task 介紹到這里,本文參考了 http://www.cplusplus.com/reference/future/packaged_task/ 相關的內容。后一篇文章我將向大家介紹 std::future,std::shared_future 以及 std::future_error,另外還會介紹 <future> 頭文件中的 std::async,std::future_category 函數以及相關枚舉類型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM