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,packaged_task,async是同步数据的高级抽象,抽象层级依次增加,同时适用面也越来越窄。
promise可能由条件变量实现,在需要的时候设置结果;packaged_task则包装了可调用对象,在任务结束时设置结果;packaged_task还需要调用get_future来获得future,async函数则直接返回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() 完成前不开始

下面是用 packaged_task 实现的功能类似于以 launch::async 作为参数的async:

template <typename Fn, typename... Args>
std::future<typename std::result_of<Fn(Args&&...)>::type>
my_async(Fn&& fn, Args&&... args)
{
	typedef typename std::result_of<Fn(Args&&...)>::type result_type;
	std::packaged_task<result_type(Args&&...)> task(std::forward<Fn>(fn));
	std::future<result_type> res(task.get_future());
	std::thread t(std::move(task), std::forward<Args>(args)...);
	t.detach();
	return std::move(res);
}

参考

cppreference.com
cplusplus.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值