[C++] - C++11 多線程 - Future

整理轉自:https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/tree/master/zh/chapter6-Future

Table of Contents

1  頭文件摘要

1.1  頭文件摘要

1.2 std::future_error 類摘要

1.3 std::promise 類摘要

1.4 std::future 類摘要

1.5 std::shared_future 類摘要

1.6 std::async 函數摘要

1.7 std::packaged_task 類摘要

2 異步任務提供者(Provider) - std::promise

2.1 std::promise 類概述

2.2 std::promise 構造函數

2.3 std::promise::get_future 介紹

2.4 std::promise::set_value 介紹

2.5 std::promise::set_exception 介紹

2.6 std::promise::set_value_at_thread_exit 介紹

2.7 std::promise::swap 介紹

3 異步任務提供者(Provider) - std::packaged_task

3.1 std::packaged_task 構造函數

3.2 std::packaged_task::valid 介紹

3.3 std::packaged_task::get_future 介紹

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

3.5 std::packaged_task::make_ready_at_thread_exit 介紹

3.6 std::packaged_task::reset() 介紹

3.7 std::packaged_task::swap() 介紹

4 Future 類型詳解

4.1 std::future 詳解

4.1.1 std::future 概述

4.1.2 std::future 構造函數

4.1.3 std::future::share()

4.1.4 std::future::get()

4.1.5 std::future::valid()

4.1.6 std::future::wait()

4.1.7 std::future::wait_for()

4.1.8 std::future::wait_until()

4.2 std::shared_future 介紹

4.2.1 std::shared_future 構造函數

4.2.2 std::shared_future 其他成員函數

5 與異步任務相關的類型介紹

5.1 std::future_error 介紹

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

5.2.1 std::future_errc 類型

5.2.2 std::future_status 類型

5.2.3 std::launch 類型

6 異步任務輔助函數介紹

6.1 std::async() 函數介紹


C++11 標準中與異步任務系相關的類型主要是以下四種 std::promisestd::packaged_taskstd::promisestd::packaged_task 也稱爲異步任務的提供者 Provider,此外 std::async 也可以作爲異步任務的提供者,不過 std::async 並不是類,而是函數),std::futurestd::shared_future。另外 <future> 中還定義一些輔助的類,例如: std::future_errorstd::future_errcstd::statusstd::launch

<future> 頭文件摘要

1.1 <future> 頭文件摘要

namespace std {
    enum class future_errc {
        broken_promise,
        future_already_retrieved,
        promise_already_satisfied,
        no_state    
    };
    
    enum class launch : unspecified {
        async = unspecified,
        deferred = unspecified,
        implementation-defined
    };
    
    enum class future_status {
        ready,
        timeout,
        deferred
    };
    
    template <> struct is_error_code_enum<future_errc> : public true_type { };
    error_code make_error_code(future_errc e);
    error_condition make_error_condition(future_errc e);

    const error_category& future_category();

    class future_error;

    template <class R> class promise;
    template <class R> class promise<R&>;
    template <> class promise<void>;

    template <class R>
    void swap(promise<R>& x, promise<R>& y);

    template <class R, class Alloc>
    struct uses_allocator<promise<R>, Alloc>;

    template <class R> class future;
    template <class R> class future<R&>;
    template <> class future<void>;
    
    template <class R> class shared_future;
    template <class R> class shared_future<R&>;
    template <> class shared_future<void>;

    template <class> class packaged_task; // undefined
    template <class R, class... ArgTypes>
    class packaged_task<R(ArgTypes...)>;

    template <class R>
    void swap(packaged_task<R(ArgTypes...)&, packaged_task<R(ArgTypes...)>&);

    template <class R, class Alloc>
    struct uses_allocator<packaged_task<R>, Alloc>;

    template <class F, class... Args>
    future<typename result_of<F(Args...)>::type>
    async(F&& f, Args&&... args);

    template <class F, class... Args>
    future<typename result_of<F(Args...)>::type>
    async(launch policy, F&& f, Args&&... args);
}

1.2 std::future_error 類摘要

namespace std {
    class future_error : public logic_error {
        public:
            future_error(error_code ec); // exposition only
            const error_code& code() const noexcept;
            const char* what() const noexcept;
    };
}

1.3 std::promise 類摘要

namespace std {
    template <class R>
    class promise {
        public:
            promise();
            template <class Allocator>
            promise(allocator_arg_t, const Allocator& a);
            promise(promise&& rhs) noexcept;
            promise(const promise& rhs) = delete;
            ~promise();

            // assignment
            promise& operator=(promise&& rhs) noexcept;
            promise& operator=(const promise& rhs) = delete;

            void swap(promise& other) noexcept;

            // retrieving the result
            future<R> get_future();

            // setting the result
            void set_value(see below);
            void set_exception(exception_ptr p);

            // setting the result with deferred notification
            void set_value_at_thread_exit(const R& r);
            void set_value_at_thread_exit(see below);
            void set_exception_at_thread_exit(exception_ptr p);
    };
    template <class R>
    void swap(promise<R>& x, promise<R>& y);
    template <class R, class Alloc>
    struct uses_allocator<promise<R>, Alloc>;
}

1.4 std::future 類摘要

namespace std {
    template <class R>
    class future {
        public:
            future();
            future(future &&);
            future(const future& rhs) = delete;
            ~future();

            future& operator=(const future& rhs) = delete;
            future& operator=(future&&) noexcept;
            shared_future<R> share() &&;

            // retrieving the value
            see below get();

            // functions to check state
            bool valid() const;
            void wait() const;
            template <class Rep, class Period>
            future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const;
            template <class Clock, class Duration>
            future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
    };
}

1.5 std::shared_future 類摘要

namespace std {
    template <class R>
    class shared_future {
        public:
            shared_future() noexcept;
            shared_future(const shared_future& rhs);
            shared_future(future<R>&&) noexcept;
            shared_future(shared_future&& rhs) noexcept;
            ~shared_future();

            shared_future& operator=(const shared_future& rhs);
            shared_future& operator=(shared_future&& rhs);

            // retrieving the value
            see below get() const;

            // functions to check state
            bool valid() const;
            void wait() const;
            template <class Rep, class Period>
            future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const;
            template <class Clock, class Duration>
            future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;
    };
}

1.6 std::async 函數摘要

namespace std {
    template <class F, class... Args>
    future<typename result_of<F(Args...)>::type>
    async(F&& f, Args&&... args);

    template <class F, class... Args>
    future<typename result_of<F(Args...)>::type>
    async(launch policy, F&& f, Args&&... args);
}

1.7 std::packaged_task 類摘要

namespace std {
    template<class> class packaged_task; // undefined

    template<class R, class... ArgTypes>
    class packaged_task<R(ArgTypes...)> {
        public:
            typedef R result_type;

            // construction and destruction
            packaged_task() noexcept;
            template <class F>
            explicit packaged_task(F f);
            template <class F, class Allocator>
            explicit packaged_task(allocator_arg_t, const Allocator& a, F f);
            explicit packaged_task(R(*f)(ArgTypes...));
            template <class F>
            explicit packaged_task(F&& f);
            template <class F, class Allocator>
            explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);
            ~packaged_task();

            // no copy
            packaged_task(packaged_task&) = delete;
            packaged_task& operator=(packaged_task&) = delete;

            // move support
            packaged_task(packaged_task&& other) noexcept;
            packaged_task& operator=(packaged_task&& other);
            void swap(packaged_task& other) noexcept;
            bool valid() const noexcept;

            // result retrieval
            future<R> get_future();
            
            // execution
            void operator()(ArgTypes... );
            void make_ready_at_thread_exit(ArgTypes...);
            void reset();
    };

    template <class R, class... ArgTypes>
    void swap(packaged_task<R(ArgTypes...)>& x, packaged_task<R(ArgTypes...)>& y) noexcept;
    template <class R, class Alloc>
    struct uses_allocator<packaged_task<R>, Alloc>;
}

<future> 頭文件中包含了以下幾個類和函數:

  1. Providers 類:std::promisestd::package_task
  2. Futures 類:std::futurestd::shared_future.
  3. Providers 函數:std::async()
  4. 其他類型:std::future_errorstd::future_errcstd::future_statusstd::launch.

2 異步任務提供者(Provider) - std::promise

2.1 std::promise 類概述

Promise 對象可以保存某一類型 T 的值,該值可被 future 對象讀取(可能在另外一個線程中),因此 promise 也提供了一種線程同步的手段。在 promise 對象構造時可以和一個共享狀態(通常是std::future)相關聯,並可以在相關聯的共享狀態(std::future)上保存一個類型爲 T 的值。

可以通過 get_future 來獲取與該 promise 對象相關聯的 future 對象,調用該函數之後,兩個對象共享相同的共享狀態(shared state)

  1. promise 對象是異步 Provider,它可以在某一時刻設置共享狀態的值。
  2. future 對象可以異步返回共享狀態的值,或者在必要的情況下阻塞調用者並等待共享狀態標誌變爲 ready,然後才能獲取共享狀態的值。

下面以一個簡單的例子來說明上述關係:

#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_int(std::future<int>& fut) {
    int x = fut.get(); // 獲取共享狀態的值.
    std::cout << "value: " << x << '\n'; // 打印 value: 10.
}

int main ()
{
    std::promise<int> prom; // 生成一個 std::promise<int> 對象.
    std::future<int> fut = prom.get_future(); // 和 future 關聯.
    std::thread t(print_int, std::ref(fut)); // 將 future 交給另外一個線程t.
    prom.set_value(10); // 設置共享狀態的值, 此處和線程t保持同步.
    t.join();
    return 0;
}

2.2 std::promise 構造函數

default (1) promise();
with allocator (2) template <class Alloc> promise (allocator_arg_t aa, const Alloc& alloc);
copy [deleted] (3) promise (const promise&) = delete;
move (4) promise (promise&& x) noexcept;
  1. 默認構造函數,初始化一個空的共享狀態。
  2. 帶自定義內存分配器的構造函數,與默認構造函數類似,但是使用自定義分配器來分配共享狀態。
  3. 拷貝構造函數,被禁用。
  4. 移動構造函數。

另外,std::promise 的 operator= 沒有拷貝語義,即 std::promise 普通的賦值操作被禁用,operator= 只有 move 語義,所以 std::promise 對象是禁止拷貝的。

例子:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

std::promise<int> prom;

void print_global_promise () {
    std::future<int> fut = prom.get_future();
    int x = fut.get();
    std::cout << "value: " << x << '\n';
}

int main ()
{
    std::thread th1(print_global_promise);
    prom.set_value(10);
    th1.join();

    prom = std::promise<int>();    // prom 被move賦值爲一個新的 promise 對象.

    std::thread th2 (print_global_promise);
    prom.set_value (20);
    th2.join();

  return 0;
}

2.3 std::promise::get_future 介紹

該函數返回一個與 promise 共享狀態相關聯的 future 。返回的 future 對象可以訪問由 promise 對象設置在共享狀態上的值或者某個異常對象。只能從 promise 共享狀態獲取一個 future 對象。在調用該函數之後,promise 對象通常會在某個時間點準備好(設置一個值或者一個異常對象),如果不設置值或者異常,promise 對象在析構時會自動地設置一個 future_error 異常(broken_promise)來設置其自身的準備狀態。上面的例子中已經提到了 get_future,此處不再重複。

2.4 std::promise::set_value 介紹

generic template (1)
void set_value (const T& val);
void set_value (T&& val);
specializations (2)
void promise<R&>::set_value (R& val);   // when T is a reference type (R&)
void promise<void>::set_value (void);   // when T is void

設置共享狀態的值,此後 promise 的共享狀態標誌變爲 ready.

2.5 std::promise::set_exception 介紹

爲 promise 對象設置異常,此後 promise 的共享狀態變標誌變爲 ready,例子如下,線程1從終端接收一個整數,線程 2 將該整數打印出來,如果線程 1 接收一個非整數,則爲 promise 設置一個異常(failbit) ,線程 2 在 std::future::get 時拋出該異常。

  #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;
  }

2.6 std::promise::set_value_at_thread_exit 介紹

設置共享狀態的值,但是不將共享狀態的標誌設置爲 ready,當線程退出時該 promise 對象會自動設置爲 ready。如果某個 std::future 對象與該 promise 對象的共享狀態相關聯,並且該 future 對象正在調用 get,則調用 get 的線程會被阻塞,當線程退出時,調用 future::get 的線程解除阻塞,同時 get 返回 set_value_at_thread_exit 所設置的值。注意,該函數已經設置了 promise 共享狀態的值,如果在線程結束之前有其他設置或者修改共享狀態的值的操作,則會拋出 future_errorpromise_already_satisfied )。

2.7 std::promise::swap 介紹

交換 promise 的共享狀態。

3 異步任務提供者(Provider) - std::packaged_task

本文主要介紹 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 對象。在調用該函數之後,兩個對象共享相同的共享狀態,具體解釋如下:

  1. std::packaged_task 對象是異步 Provider,它在某一時刻通過調用被包裝的任務來設置共享狀態的值。
  2. 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;
}

執行結果爲:

10
9
8
7
6
5
4
3
2
1
Finished!
the countdown lasted for 10 seconds.

3.1 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 賦值運算。

3.2 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;
    }

3.3 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;
}

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

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

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

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

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

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

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

3.5 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_errorpromise_already_satisfied )。

3.6 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;
}

3.7 std::packaged_task::swap() 介紹

交換 packaged_task 的共享狀態。

4 Future 類型詳解

本文主要介紹 std::futurestd::shared_future 以及 std::future_error,另外還會介紹 <future> 頭文件中的 std::asyncstd::future_category 函數以及相關枚舉類型。

4.1 std::future 詳解

4.1.1 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 可以是函數或者類,其實我們前面都已經提到了,他們分別是:

  1. std::async 函數,本文後面會介紹 std::async() 函數。
  2. std::promise::get_futureget_future 爲 promise 類的成員函數,詳見 C++11 併發指南四(<future> 詳解一 std::promise 介紹)。
  3. std::packaged_task::get_future,此時 get_future 爲 packaged_task 的成員函數,詳見C++11 併發指南四(<future> 詳解二 std::packaged_task 介紹)。

一個 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;
}

4.1.2 std::future 構造函數

std::future 一般由 std::asyncstd::promise::get_futurestd::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-賦值操作

4.1.3 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;
}

4.1.4 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,則調用該函數會阻塞當前的調用者,而此後一旦共享狀態的標誌變爲 readyget 返回 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;
}

4.1.5 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;
}

4.1.6 std::future::wait()

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

如果共享狀態的標誌不是 ready(此時 Provider 沒有在共享狀態上設置值(或者異常)),調用該函數會被阻塞當前線程,直到共享狀態的標誌變爲 ready。

一旦共享狀態的標誌變爲 readywait() 函數返回,當前線程被解除阻塞,但是 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 ) 

4.1.7 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_for() 返回,返回值如下:

返回值 描述
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::mi    lliseconds 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;
}

4.1.8 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_until() 返回,返回值如下:

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

4.2 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.

4.2.1 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)。

4.2.2 std::shared_future 其他成員函數

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

  • operator=(): 賦值操作符,與 std::future 的賦值操作不同,std::shared_future 除了支持 move 賦值操作外,還支持普通的賦值操作。
  • get(): 獲取與該 std::shared_future 對象相關聯的共享狀態的值(或者異常)。
  • valid(): 有效性檢查。
  • wait(): 等待與該 std::shared_future 對象相關聯的共享狀態的標誌變爲 ready。
  • wait_for(): 等待與該 std::shared_future 對象相關聯的共享狀態的標誌變爲 ready。(等待一段時間,超過該時間段wait_for 返回。)
  • wait_until(): 等待與該 std::shared_future 對象相關聯的共享狀態的標誌變爲 ready。(在某一時刻前等待,超過該時刻 wait_until 返回。)

5 與異步任務相關的類型介紹

5.1 std::future_error 介紹

class future_error : public logic_error;

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

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

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

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

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

5.2.1 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 無共享狀態。

5.2.2 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 函數。

5.2.3 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:
**********@@@@@@@@@@

6 異步任務輔助函數介紹

6.1 std::async() 函數介紹

與 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::asynclaunch::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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章