C++ 标准库 future
下面对 C++11 起 <future>
头文件提供的异步编程与并发任务支持做一次系统、深入的梳理,包括核心类型、任务启动方式、结果获取与等待机制、异常传播,以及实践建议。
一、概述
<future>
主要用于异步任务的调度与结果获取,核心在于将“任务提交”与“结果消费”拆分为两个角色:- 发起者(Producer):将任务打包、提交,并最终“设置”结果或异常;
- 消费者(Consumer):通过
std::future
或std::shared_future
等对象等待任务完成并获取结果。
- 它的设计遵循延迟执行、同步等待和异常传递三大原则,能与
std::thread
、线程池、事件循环等底层机制配合使用。
二、核心类型与概念
类型 | 说明 |
---|---|
std::future<R> | 区分任务执行者和结果使用者,可一次性获取异步结果 R 或重新抛出异常。 |
std::promise<R> | 与 std::future<R> 配对,负责在任务结束后设置值或异常。 |
std::shared_future<R> | 可复制、可多次获取同一任务结果的未来对象。 |
std::packaged_task<R()> | 将可调用对象包装成可传递给线程或执行器的“任务”,并自动生成 future 。 |
std::async | 最简便的异步任务启动接口,返回 std::future<R> 。 |
std::launch | 启动策略枚举:async (新线程)/deferred (延迟到 get() 时执行) |
三、std::future
与 std::promise
1. std::promise<R>
- 功能:在某个线程或回调中,调用
promise.set_value(val); // 或者 promise.set_exception(std::make_exception_ptr(ex));
来传递计算结果或异常。 - 与
std::future
配对:std::promise<int> p; std::future<int> f = p.get_future(); // 子线程中执行 p.set_value(42); // 主线程中通过 f.get() 获取 42 并捕获异常
2. std::future<R>
- 获取结果:
R get()
:阻塞等待直到有值或异常,之后 一次性 获取并销毁共享状态;再次调用会抛std::future_error
.void wait()
:仅等待完成,不取值。wait_for(dur)
/wait_until(tp)
:带超时等待,返回std::future_status
。
- 状态查询:
valid()
:判断是否持有共享状态(对应get_future()
或async
返回的合法性)。share()
:将future
转为shared_future
。
四、std::async
与启动策略
enum class launch { async = 1, deferred = 2 };
std::future<R> std::async(launch policy, F&& f, Args&&... args)
policy = std::launch::async
:总是异步启动,新线程执行。policy = std::launch::deferred
:延迟启动,直到调用future.get()
、wait()
时才在当前线程执行f(args...)
。policy
可按位或,若底层支持由实现决定策略。
- 示例:
auto f1 = std::async(std::launch::async, []{ return compute(); }); auto f2 = std::async(std::launch::deferred, []{ return compute(); }); // f2 在此之前不会启动,即便是不同线程
五、std::packaged_task
与 std::shared_future
1. std::packaged_task<R()>
- 功能:将任意可调用对象包装为可调用的任务,并内建一个与之配对的
std::future<R>
。 - 用法:
std::packaged_task<int(int,int)> task([](int a,int b){ return a+b; }); std::future<int> f = task.get_future(); // 将 task 移入线程 std::thread t(std::move(task), 2, 3); int sum = f.get(); // 5 t.join();
2. std::shared_future<R>
- 复制语义:多个消费者可同时
.get()
同一结果;适用于广播式通知。 - 获取方式:由
std::future<R>
调用share()
或直接由std::promise
生成:auto sf = p.get_future().share();
六、错误与异常传播
- 异常封装:若任务内部抛出未捕获异常,运行时会自动通过对应的
promise
或async
捕获并储存为exception_ptr
,在future.get()
时重新抛出。 - 示例:
auto f = std::async([]{ throw std::runtime_error("error"); return 0; }); try { f.get(); } catch (const std::exception& e) { std::cerr << "Caught: " << e.what() << "\n"; }
七、常用接口汇总
接口 | 说明 |
---|---|
future.get() | 获取结果或抛出异常,一次性有效 |
future.wait() | 阻塞直到任务完成 |
future.wait_for(dur) | 等待指定时长,返回 std::future_status |
future.wait_until(tp) | 等待至指定时刻 |
future.valid() | 判断是否持有共享状态 |
future.share() | 转为 shared_future |
promise.set_value(v) | 设置正常结果 |
promise.set_exception(eptr) | 设置异常 |
packaged_task.get_future() | 获取与任务绑定的 future |
async(launch, f, args...) | 按策略启动异步任务,返回 future |
八、示例:多任务并行求和
#include <future>
#include <vector>
#include <numeric>
int parallel_sum(const std::vector<int>& v) {
auto mid = v.size() / 2;
// 左右两部分异步求和
auto f1 = std::async(std::launch::async,
[&v,mid]{ return std::accumulate(v.begin(), v.begin()+mid, 0); });
auto f2 = std::async(std::launch::async,
[&v,mid]{ return std::accumulate(v.begin()+mid, v.end(), 0); });
return f1.get() + f2.get();
}
九、实践建议
- 控制并发粒度
- 不要对每个小任务都用
async(async)
启动新线程,易导致过度线程上下文切换。可用线程池封装或限制并发度。
- 不要对每个小任务都用
- 选择合适策略
- 对开销大或 I/O 密集型任务用
std::launch::async
;对于轻量短任务可考虑deferred
或同步直接调用。
- 对开销大或 I/O 密集型任务用
- 使用
shared_future
- 当多个消费者都须等待同一结果时,用
shared_future
避免重复执行或转移所有权失败。
- 当多个消费者都须等待同一结果时,用
- 注意生命周期
std::packaged_task
与std::promise
不可复制,需用std::move
传递;析构前必须设置结果,否则调用get()
会抛future_error
。
- 及时调用
get()
- 如果不需要结果也应调用
get()
或wait()
,否则底层资源(如线程)可能一直占用直到对象析构。
- 如果不需要结果也应调用
- 异常安全
- 在使用
promise
时,若可能提前退出,要确保在所有路径中都调用set_value
或set_exception
,否则对应的get()
会挂起。
- 在使用
- 避免死锁
- 在
future.get()
前不要持有可能与任务内部也要获取的锁,否则可能交叉等待。
- 在
通过以上对 <future>
中异步任务包装、结果获取、异常传播与实践要点的全面梳理,相信你能在并行计算、异步 I/O、任务依赖等场景中高效、稳健地使用标准库的异步支持。祝编码顺利!