c++11線程支持庫:future

future 頭文件中包含:
Future 類:std::future,shared_future
Provider 類模板:std::promise,std::packaged_task
Provider 函數模板:std::async()

一個 future 對象可以從 provider 對象或函數取得一個值,provider 對象或函數可能位於不同的線程。
有效的 future 對象和一個共享狀態相關聯,通過調用以下函數之一構造:
async
promise::get_future
packaged_task::get_future

promise 和 future

先來了解<promise,future>,promise 和 future 的概要聲明如下:

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();

	// 賦值
	promise& operator=(promise&& rhs) noexcept;
	promise& operator=(const promise& rhs) = delete;
	void swap(promise& other) noexcept;

	// 取得結果
	future<R> get_future();

	// 設置結果
	void set_value(const R& value);
	void set_value(R&& value);
	void set_exception(exception_ptr p);

	// 以延遲通知設置結果
	void set_value_at_thread_exit(const R& value);
	void set_value_at_thread_exit(R&& value);
	void set_exception_at_thread_exit(exception_ptr p);
};

// 特化
template <class R> class promise<R&>;
template <> class promise<void>;

// promise<R&> 模板特化的 set_value 和 set_value_at_thread_exit 爲以下版本:
void set_value(R& value);
void set_value_at_thread_exit(R& value);

// promise<void> 模板特化的 set_value 和 set_value_at_thread_exit 爲以下版本:
void set_value();
void set_value_at_thread_exit();

在 promise 對象構造時和一個共享狀態相關聯,通過 get_future 來獲取與該 promise 對象相關聯的 future 對象。

template <class R>
class future {
 public:
	future() noexcept;
	future(future &&) noexcept;
	future(const future& rhs) = delete;
	~future();
	future& operator=(const future& rhs) = delete;
	future& operator=(future&&) noexcept;
	shared_future<R> share();
 
	// 取得值
	R get();
 
	// 用以檢查狀態的函數
	bool valid() const noexcept;
	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;
};

// 特化
template <class R> class future<R&>;
template <> class future<void>;

// future<R&> 模板特化的 get 爲以下版本:
R& get();
// future<void> 模板特化的 get 爲以下版本:
void get();

默認構造的 future 對象不是 valid 的,除非一個 valid future 對象通過 move 構造或 move 賦值而來。
對一個 valid future 對象調用 future::get() 將會阻塞,直到 provider 將共享狀態置爲 ready 狀態(通過設置一個值或一個異常)。

promise::set_value_at_thread_exit() 原子地存儲 value 到共享狀態,而不立即令狀態就緒。在當前線程退出時,銷燬所有擁有線程局域存儲期的變量後,再令狀態就緒。
promise::set_exception_at_thread_exit() 存儲異常指針 p 到共享狀態,而不立即使狀態就緒。在當前線程退出時,銷燬所有擁有線程局域存儲期的變量後,再令狀態就緒。

future::get() 返回後將會使 valid() 變爲 false 。
future::wait 系列函數會阻塞線程直至結果變得可用。

一個簡單的例子:

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

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

int main ()
{
	std::promise<int> prom;
	std::future<int> fut = prom.get_future();
	std::thread t(print_int, std::ref(fut));
	std::this_thread::sleep_for(std::chrono::seconds(1));
	prom.set_value(10);
	t.join();
	return 0;
}
enum class future_errc {
	broken_promise = /* 由實現定義 */, /*the promise object with which the future shares its shared state
                                       was destroyed before being set a value or an exception.*/
	future_already_retrieved = /* 由實現定義 */,
	promise_already_satisfied = /* 由實現定義 */,
	no_state = /* 由實現定義 */ /*An operation attempted to access the shared state
	                            of an object with no shared state.*/
};

promise對象在設置值和設置異常時,如果已經設置過值或異常,會拋出error category爲promise_already_satisfied的異常。
同一個future對象多次調用時get會拋出error category爲future_already_retrieved的異常。

future::share() 轉移 *this 的共享狀態(若存在)到 shared_future 對象。在 future 上調用 share 後 valid() == false 。
多個 shared_future 可以共享某個共享狀態的結果。

future 和 shared_future 的 wait_for 和 wait_until 函數返回值的聲明如下:

enum class future_status {
	ready,
	timeout,
	deferred // 要計算結果的函數仍未啓動, 見 async
};

packaged_task

packaged_task 包裝一個可調用的對象,並且允許異步獲取該可調用對象產生的結果。packaged_task 對象內部包含了兩個基本元素,一、被包裝的任務,任務是一個可調用的對象,二、共享狀態,用於保存任務的返回值,可以通過 future 對象來達到異步訪問共享狀態的效果。
packaged_task 的概要聲明:

template<class R, class... ArgTypes>
class packaged_task<R(ArgTypes...)> {
 public:
	// 構造與析構
	packaged_task() noexcept; // 構造無任務且無共享狀態的對象
	template <class F>
	explicit packaged_task(F&& f);
	~packaged_task();
 
	// 無複製
	packaged_task(const packaged_task&) = delete;
	packaged_task& operator=(const packaged_task&) = delete;
 
	// 移動支持
	packaged_task(packaged_task&& rhs) noexcept;
	packaged_task& operator=(packaged_task&& rhs) noexcept;
 
	void swap(packaged_task& other) noexcept;
	bool valid() const noexcept; // 檢查是否擁有共享狀態, 默認構造出的對象返回false
 
	// 結果取得
	future<R> get_future();
 
	// 執行
	void operator()(ArgTypes... args);
	void make_ready_at_thread_exit(ArgTypes... args);
 
	void reset(); / 拋棄先前執行的結果,構造一個新的共享狀態
};

packaged_task::operator()() 以 args 爲參數調用存儲的任務。任務返回值或任何拋出的異常被存儲於共享狀態。令共享狀態就緒,並解除阻塞任何等待此操作的線程。
packaged_task::make_ready_at_thread_exit() 類似 operator()(),但僅在當前線程退出,並銷燬所有線程局域存儲期對象後,才令共享狀態就緒。
來一個簡單例子:

#include <future>
#include <iostream>
#include <thread>

int fib(int n)
{
    if (n < 3) return 1;
    else return fib(n-1) + fib(n-2);
}

int main()
{
    std::packaged_task<int(int)> fib_task(&fib);

    std::cout << "starting task\n";
    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();
}

async

考慮需要異步調用一個函數,並取得它的調返回結果,可以用 pacaged_task 包裝這個函數,然後調用 get_future 得到 future 對象,再調用 future 對象的 get 方法。
async 簡化了上述的步驟,它異步運行一個函數(有可能在新線程中執行),並返回保有其結果的 future 對象。

template <class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
async(Function&& f, Args&&... args); (1)
enum class launch : /* unspecified */ {
    async =    /* unspecified */,
    deferred = /* unspecified */,
};
template <class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>>
async(std::launch policy, Function&& f, Args&&... args); (2)

future 的模板參數是 f 的返回值。
若 policy 設置 launch::async 標誌,則 async 在新的執行線程執行可調用對象 f。
若 policy 設置 launch::deferred 標誌,會進行惰性求值:在 async 所返回的 future 上首次調用非定時等待函數,將導致在當前線程中,以 args… (作爲右值傳遞)的副本調用 f (亦作爲右值)的副本。
若 policy 中設置了 std::launch::async 和 std::launch::deferred 兩個標誌,則進行異步執行還是惰性求值取決於實現。
(1) 表現如同以 policy 爲 std::launch::async | std::launch::deferred 調用 (2) 。

如果共享狀態包含a deferred function,std::future::wait_for 和 std::future::wait_until 不會阻塞,立即返回 future_status::deferred。例如:

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

bool is_prime(int x)
{
    for (int i = 2; i < x; ++i)
        if (x % i == 0)
            return false;
    return true;
}

int main()
{
    std::future<bool> fut = std::async(std::launch::deferred, is_prime, 1610612741);
    std::cout << "checking, please wait\n";
    /** fut.wait_for()應返回std::future_status::deferred, 但
    gcc version 4.8.2 20140120 (Red Hat 4.8.2-15) (GCC)卻返回std::future_status::timeout */
    while(fut.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout)
    {
        std::cout << '.';
        fflush(stdout);
    }
    std::cout << "return of wait_for\n";
    bool x = fut.get();
    std::cout << (x ? "is" : "is not") << " prime.\n";

    return 0;
}

另外:

若從 std::async 獲得的 std::future 未被移動或綁定到引用,則在完整表達式結尾, std::future 的析構函數將阻塞直至異步計算完成,實質上令如下代碼同步
std::async(std::launch::async, []{ f(); }); // 臨時量的析構函數等待 f()
std::async(std::launch::async, []{ g(); }); // f() 完成前不開始

參考

cppreference.com
cplusplus.com

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