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