1. 程式人生 > >C++11 併發指南四( 詳解二 std::packaged_task 介紹)

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 函式以及相關列舉型別。