C++11 並發指南四( 詳解三 std::future & std::shared_future)


上一講《C++11 並發指南四(<future> 詳解二 std::packaged_task 介紹)》主要介紹了 <future> 頭文件中的 std::packaged_task 類,本文主要介紹 std::future,std::shared_future 以及 std::future_error,另外還會介紹 <future> 頭文件中的 std::async,std::future_category 函數以及相關枚舉類型。

std::future 介紹

前面已經多次提到過 std::future,那么 std::future 究竟是什么呢?簡單地說,std::future 可以用來獲取異步任務的結果,因此可以把它當成一種簡單的線程間同步的手段。std::future 通常由某個 Provider 創建,你可以把 Provider 想象成一個異步任務的提供者,Provider 在某個線程中設置共享狀態的值,與該共享狀態相關聯的 std::future 對象調用 get(通常在另外一個線程中) 獲取該值,如果共享狀態的標志不為 ready,則調用 std::future::get 會阻塞當前的調用者,直到 Provider 設置了共享狀態的值(此時共享狀態的標志變為 ready),std::future::get 返回異步任務的值或異常(如果發生了異常)。

一個有效(valid)的 std::future 對象通常由以下三種 Provider 創建,並和某個共享狀態相關聯。Provider 可以是函數或者類,其實我們前面都已經提到了,他們分別是:

一個 std::future 對象只有在有效(valid)的情況下才有用(useful),由 std::future 默認構造函數創建的 future 對象不是有效的(除非當前非有效的 future 對象被 move 賦值另一個有效的 future 對象)。

 在一個有效的 future 對象上調用 get 會阻塞當前的調用者,直到 Provider 設置了共享狀態的值或異常(此時共享狀態的標志變為 ready),std::future::get 將返回異步任務的值或異常(如果發生了異常)。

下面以一個簡單的例子說明上面一段文字吧(參考):

// future example
#include <iostream>             // std::cout
#include <future>               // std::async, std::future
#include <chrono>               // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool
is_prime(int x)
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int
main()
{
    // call function asynchronously:
    std::future < bool > fut = std::async(is_prime, 444444443);

    // do something while waiting for function to set future:
    std::cout << "checking, please wait";
    std::chrono::milliseconds span(100);
    while (fut.wait_for(span) == std::future_status::timeout)
        std::cout << '.';

    bool x = fut.get();         // retrieve return value

    std::cout << "\n444444443 " << (x ? "is" : "is not") << " prime.\n";

    return 0;
}

 std::future 成員函數

std::future 構造函數

std::future 一般由 std::async, std::promise::get_future, std::packaged_task::get_future 創建,不過也提供了構造函數,如下表所示:

default (1)
future() noexcept;
copy [deleted] (2)
future (const future&) = delete;
move (3)
future (future&& x) noexcept;

 

不過 std::future 的拷貝構造函數是被禁用的,只提供了默認的構造函數和 move 構造函數(注:C++ 新特新)。另外,std::future 的普通賦值操作也被禁用,只提供了 move 賦值操作。如下代碼所示:

 std::future<int> fut;           // 默認構造函數
  fut = std::async(do_some_task);   // move-賦值操作。

std::future::share()

返回一個 std::shared_future 對象(本文后續內容將介紹 std::shared_future ),調用該函數之后,該 std::future 對象本身已經不和任何共享狀態相關聯,因此該 std::future 的狀態不再是 valid 的了。

#include <iostream>       // std::cout
#include <future>         // std::async, std::future, std::shared_future

int do_get_value() { return 10; }

int main ()
{
    std::future<int> fut = std::async(do_get_value);
    std::shared_future<int> shared_fut = fut.share();

    // 共享的 future 對象可以被多次訪問.
    std::cout << "value: " << shared_fut.get() << '\n';
    std::cout << "its double: " << shared_fut.get()*2 << '\n';

    return 0;
}

std::future::get()

std::future::get 一共有三種形式,如下表所示(參考):

generic template (1)
T get();
reference specialization (2)
R& future<R&>::get();       // when T is a reference type (R&)
void specialization (3)
void future<void>::get();   // when T is void

當與該 std::future 對象相關聯的共享狀態標志變為 ready 后,調用該函數將返回保存在共享狀態中的值,如果共享狀態的標志不為 ready,則調用該函數會阻塞當前的調用者,而此后一旦共享狀態的標志變為 ready,get 返回 Provider 所設置的共享狀態的值或者異常(如果拋出了異常)。

請看下面的程序:

#include <iostream>       // std::cin, std::cout, std::ios
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future
#include <exception>      // std::exception, std::current_exception

void get_int(std::promise<int>& prom) {
    int x;
    std::cout << "Please, enter an integer value: ";
    std::cin.exceptions (std::ios::failbit);   // throw on failbit
    try {
        std::cin >> x;                         // sets failbit if input is not int
        prom.set_value(x);
    } catch (std::exception&) {
        prom.set_exception(std::current_exception());
    }
}

void print_int(std::future<int>& fut) {
    try {
        int x = fut.get();
        std::cout << "value: " << x << '\n';
    } catch (std::exception& e) {
        std::cout << "[exception caught: " << e.what() << "]\n";
    }
}

int main ()
{
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread th1(get_int, std::ref(prom));
    std::thread th2(print_int, std::ref(fut));

    th1.join();
    th2.join();
    return 0;
}

std::future::valid()

檢查當前的 std::future 對象是否有效,即釋放與某個共享狀態相關聯。一個有效的 std::future 對象只能通過 std::async(), std::future::get_future 或者 std::packaged_task::get_future 來初始化。另外由 std::future 默認構造函數創建的 std::future 對象是無效(invalid)的,當然通過 std::future 的 move 賦值后該 std::future 對象也可以變為 valid。

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

int do_get_value() { return 11; }

int main ()
{
    // 由默認構造函數創建的 std::future 對象,
    // 初始化時該 std::future 對象處於為 invalid 狀態.
    std::future<int> foo, bar;
    foo = std::async(do_get_value); // move 賦值, foo 變為 valid.
    bar = std::move(foo); // move 賦值, bar 變為 valid, 而 move 賦值以后 foo 變為 invalid.

    if (foo.valid())
        std::cout << "foo's value: " << foo.get() << '\n';
    else
        std::cout << "foo is not valid\n";

    if (bar.valid())
        std::cout << "bar's value: " << bar.get() << '\n';
    else
        std::cout << "bar is not valid\n";

    return 0;
}

std::future::wait()

等待與當前std::future 對象相關聯的共享狀態的標志變為 ready.

如果共享狀態的標志不是 ready(此時 Provider 沒有在共享狀態上設置值(或者異常)),調用該函數會被阻塞當前線程,直到共享狀態的標志變為 ready。
一旦共享狀態的標志變為 ready,wait() 函數返回,當前線程被解除阻塞,但是 wait() 並不讀取共享狀態的值或者異常。下面的代碼說明了 std::future::wait() 的用法(參考

#include <iostream>                // std::cout
#include <future>                // std::async, std::future
#include <chrono>                // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 為了體現效果, 該函數故意沒有優化.
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int main()
{
    // call function asynchronously:
    std::future < bool > fut = std::async(do_check_prime, 194232491);

    std::cout << "Checking...\n";
    fut.wait();

    std::cout << "\n194232491 ";
    if (fut.get()) // guaranteed to be ready (and not block) after wait returns
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}

執行結果如下:

concurrency ) ./Future-wait 
Checking...

194232491 is prime.
concurrency ) 

std::future::wait_for()

與 std::future::wait() 的功能類似,即等待與該 std::future 對象相關聯的共享狀態的標志變為 ready,該函數原型如下:

template <class Rep, class Period>
  future_status wait_for (const chrono::duration<Rep,Period>& rel_time) const;

而與 std::future::wait() 不同的是,wait_for() 可以設置一個時間段 rel_time,如果共享狀態的標志在該時間段結束之前沒有被 Provider 設置為 ready,則調用 wait_for 的線程被阻塞,在等待了 rel_time 的時間長度后 wait_until() 返回,返回值如下:

返回值 描述
future_status::ready 共享狀態的標志已經變為 ready,即 Provider 在共享狀態上設置了值或者異常。
future_status::timeout 超時,即在規定的時間內共享狀態的標志沒有變為 ready。
future_status::deferred 共享狀態包含一個 deferred 函數。

請看下面的例子:

#include <iostream>                // std::cout
#include <future>                // std::async, std::future
#include <chrono>                // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 為了體現效果, 該函數故意沒有優化.
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int main()
{
    // call function asynchronously:
    std::future < bool > fut = std::async(do_check_prime, 194232491);

    std::cout << "Checking...\n";
    std::chrono::milliseconds span(1000); // 設置超時間隔.

    // 如果超時,則輸出".",繼續等待
    while (fut.wait_for(span) == std::future_status::timeout)
        std::cout << '.';

    std::cout << "\n194232491 ";
    if (fut.get()) // guaranteed to be ready (and not block) after wait returns
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}

std::future::wait_until()

與 std::future::wait() 的功能類似,即等待與該 std::future 對象相關聯的共享狀態的標志變為 ready,該函數原型如下:

template <class Rep, class Period>
  future_status wait_until (const chrono::time_point<Clock,Duration>& abs_time) const;

而 與 std::future::wait() 不同的是,wait_until() 可以設置一個系統絕對時間點 abs_time,如果共享狀態的標志在該時間點到來之前沒有被 Provider 設置為 ready,則調用 wait_until 的線程被阻塞,在 abs_time 這一時刻到來之后 wait_for() 返回,返回值如下:

返回值 描述
future_status::ready 共享狀態的標志已經變為 ready,即 Provider 在共享狀態上設置了值或者異常。
future_status::timeout 超時,即在規定的時間內共享狀態的標志沒有變為 ready。
future_status::deferred 共享狀態包含一個 deferred 函數。

 

std::shared_future 介紹

std::shared_future 與 std::future 類似,但是 std::shared_future 可以拷貝、多個 std::shared_future 可以共享某個共享狀態的最終結果(即共享狀態的某個值或者異常)。shared_future 可以通過某個 std::future 對象隱式轉換(參見 std::shared_future 的構造函數),或者通過 std::future::share() 顯示轉換,無論哪種轉換,被轉換的那個 std::future 對象都會變為 not-valid.

std::shared_future 構造函數

std::shared_future 共有四種構造函數,如下表所示:

default (1)
shared_future() noexcept;
copy (2)
shared_future (const shared_future& x);
move (3)
shared_future (shared_future&& x) noexcept;
move from future (4)
shared_future (future<T>&& x) noexcept;

最后 move from future(4) 即從一個有效的 std::future 對象構造一個 std::shared_future,構造之后 std::future 對象 x 變為無效(not-valid)。

std::shared_future 其他成員函數

std::shared_future 的成員函數和 std::future 大部分相同,如下(每個成員函數都給出了連接):

std::future_error 介紹

class future_error : public logic_error;

std::future_error 繼承子 C++ 標准異常體系中的 logic_error,有關 C++ 異常的繼承體系,請參考相關的C++教程 ;-)。

其他與 std::future 相關的函數介紹

與 std::future 相關的函數主要是 std::async(),原型如下:

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

上面兩組 std::async() 的不同之處是第一類 std::async 沒有指定異步任務(即執行某一函數)的啟動策略(launch policy),而第二類函數指定了啟動策略,詳見 std::launch 枚舉類型,指定啟動策略的函數的 policy 參數可以是launch::async,launch::deferred,以及兩者的按位或( | )。

std::async() 的 fn 和 args 參數用來指定異步任務及其參數。另外,std::async() 返回一個 std::future 對象,通過該對象可以獲取異步任務的值或異常(如果異步任務拋出了異常)。

下面介紹一下 std::async 的用法。

#include <stdio.h>
#include <stdlib.h>

#include <cmath>
#include <chrono>
#include <future>
#include <iostream>

double ThreadTask(int n) {
    std::cout << std::this_thread::get_id()
        << " start computing..." << std::endl;

    double ret = 0;
    for (int i = 0; i <= n; i++) {
        ret += std::sin(i);
    }

    std::cout << std::this_thread::get_id()
        << " finished computing..." << std::endl;
    return ret;
}

int main(int argc, const char *argv[])
{
    std::future<double> f(std::async(std::launch::async, ThreadTask, 100000000));

#if 0
    while(f.wait_until(std::chrono::system_clock::now() + std::chrono::seconds(1))
            != std::future_status::ready) {
        std::cout << "task is running...\n";
    }
#else
    while(f.wait_for(std::chrono::seconds(1))
            != std::future_status::ready) {
        std::cout << "task is running...\n";
    }
#endif

    std::cout << f.get() << std::endl;

    return EXIT_SUCCESS;
}

 

其他與 std::future 相關的枚舉類介紹

下面介紹與 std::future 相關的枚舉類型。與 std::future 相關的枚舉類型包括:

enum class future_errc;
enum class future_status;
enum class launch;

下面分別介紹以上三種枚舉類型:

std::future_errc 類型

std::future_errc 類型描述如下(參考):

類型
取值
描述
broken_promise 0 與該 std::future 共享狀態相關聯的 std::promise 對象在設置值或者異常之前一被銷毀。
future_already_retrieved 1 與該 std::future 對象相關聯的共享狀態的值已經被當前 Provider 獲取了,即調用了 std::future::get 函數。
promise_already_satisfied 2 std::promise 對象已經對共享狀態設置了某一值或者異常。
no_state 3 無共享狀態。

std::future_status 類型(參考

std::future_status 類型主要用在 std::future(或std::shared_future)中的 wait_for 和 wait_until 兩個函數中的。

類型 取值
描述
future_status::ready 0 wait_for(或wait_until) 因為共享狀態的標志變為 ready 而返回。
future_status::timeout 1 超時,即 wait_for(或wait_until) 因為在指定的時間段(或時刻)內共享狀態的標志依然沒有變為 ready 返回。
future_status::deferred 2 共享狀態包含了 deferred 函數。

std::launch 類型

該枚舉類型主要是在調用 std::async 設置異步任務的啟動策略的。

類型 描述
launch::async Asynchronous: 異步任務會在另外一個線程中調用,並通過共享狀態返回異步任務的結果(一般是調用 std::future::get() 獲取異步任務的結果)。
launch::deferred Deferred: 異步任務將會在共享狀態被訪問時調用,相當與按需調用(即延遲(deferred)調用)。

請看下例(參考):

#include <iostream>                // std::cout
#include <future>                // std::async, std::future, std::launch
#include <chrono>                // std::chrono::milliseconds
#include <thread>                // std::this_thread::sleep_for

void
do_print_ten(char c, int ms)
{
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(ms));
        std::cout << c;
    }
}

int
main()
{
    std::cout << "with launch::async:\n";
    std::future < void >foo =
        std::async(std::launch::async, do_print_ten, '*', 100);
    std::future < void >bar =
        std::async(std::launch::async, do_print_ten, '@', 200);
    // async "get" (wait for foo and bar to be ready):
    foo.get();
    bar.get();
    std::cout << "\n\n";

    std::cout << "with launch::deferred:\n";
    foo = std::async(std::launch::deferred, do_print_ten, '*', 100);
    bar = std::async(std::launch::deferred, do_print_ten, '@', 200);
    // deferred "get" (perform the actual calls):
    foo.get();
    bar.get();
    std::cout << '\n';

    return 0;
}

在我的機器上執行結果:

with launch::async:
*@**@**@**@**@*@@@@@

with launch::deferred:
**********@@@@@@@@@@

 


免責聲明!

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



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