C++并发编程:future
承接上一篇的条件变量,我们现在学习C++11并发库的最后一个板块future。前面 thread 没法直接拿到线程函数返回值,future 系列就是用来解决「子线程运算结果传递给主线程」。
1:前置说明
future 体系是 C++11 为解决 **"线程返回值传递" 和 "异步任务管理"** 设计的标准库,核心解决原生std::thread无法直接获取返回值、无法安全传递异常的痛点。
包含 5 个核心组件:
std::future<T>:结果消费者,读取异步任务的结果std::promise<T>:结果生产者,手动写入异步任务的结果 / 异常std::shared_future<T>:可共享的结果消费者,支持多线程同时读取std::packaged_task<R(Args...)>:任务包装器,将任意可调用对象包装成异步任务std::async:任务启动器,一键创建异步任务并返回 future
2:std::future<T>
1:总
std::future<T>是只读的结果句柄,用于获取异步任务的返回值或异常。他本身不存储数据,只是指向底层共享状态的指针。
2:核心API列表
| 接口 | 作用 | 参数 | 返回值 | 注意事项 |
|---|---|---|---|---|
future() noexcept |
默认构造函数,创建一个无效的 future | 无 | 无 | 调用任何成员函数都会抛异常 |
future(future&& other) noexcept |
移动构造函数 | other:要移动的 future |
无 | 移动后other变为无效 |
future& operator=(future&& other) noexcept |
移动赋值运算符 | other:要移动的 future |
自身引用 | 移动后other变为无效 |
T get() |
阻塞等待结果就绪,然后返回结果 | 无 | 异步任务的返回值 | 1. 只能调用一次2. 结果未就绪会阻塞3. 任务抛异常会在这里重新抛出 |
void wait() const |
只阻塞等待结果就绪,不取值 | 无 | 无 | 可以多次调用 |
template<class Rep, class Period><br>future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const |
超时等待结果就绪 | rel_time:等待的最长时长 |
future_status枚举:- ready:结果就绪- timeout:超时未就绪- deferred:任务是延迟执行的 |
可以多次调用 |
template<class Clock, class Duration><br>future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const |
等待到指定时间点 | abs_time:等待的截止时间 |
同上 | 可以多次调用 |
shared_future<T> share() noexcept |
转换为shared_future |
无 | 对应的shared_future对象 |
转换后原 future 变为无效 |
bool valid() const noexcept |
检查 future 是否有效(是否指向共享状态) | 无 | true有效,false无效 |
移动或调用get()后变为无效 |
3:常用示例
#include <future>
#include <iostream>
using namespace std;
int main() {
// 假设已经通过promise/async获取了future<int> fu
future<int> fu = async(launch::async, [](){ return 100; });
// 检查是否有效
if (fu.valid()) {
// 超时等待1秒
auto status = fu.wait_for(chrono::seconds(1));
if (status == future_status::ready) {
cout << "结果:" << fu.get() << endl; // 只能调用一次
} else {
cout << "等待超时" << endl;
}
}
return 0;
}
4:坑
get()只能调用一次:第二次调用会抛出std::future_error异常- 无效 future 调用任何成员函数都会抛异常:移动、调用
get()或share()后,future 变为无效 wait()和wait_for()不会消耗结果:可以多次调用,只有get()会消耗结果
3:std::promise<T>
1:总述
std::promise<T>是可写的结果句柄,用于手动设置异步任务的返回值或异常。一个 promise 对应一个 future。
2:核心API列表
| 接口 | 作用 | 参数 | 返回值 | 注意事项 |
|---|---|---|---|---|
promise() |
默认构造函数,创建一个新的 promise | 无 | 无 | 自动创建底层共享状态 |
promise(promise&& other) noexcept |
移动构造函数 | other:要移动的 promise |
无 | 移动后other变为无效 |
promise& operator=(promise&& other) noexcept |
移动赋值运算符 | other:要移动的 promise |
自身引用 | 移动后other变为无效 |
future<T> get_future() |
获取对应的 future 对象 | 无 | 与该 promise 关联的 future | 1. 只能调用一次2. 调用后 promise 仍然有效 |
void set_value(const T& val) |
设置正常返回值(左值) | val:要设置的结果值 |
无 | 1. 只能调用一次2. 调用后结果变为就绪 |
void set_value(T&& val) |
设置正常返回值(右值) | val:要设置的结果值 |
无 | 同上,会移动构造结果 |
void set_exception(exception_ptr p) |
设置异常 | p:要设置的异常指针 |
无 | 1. 只能调用一次2. 调用后结果变为就绪 |
void set_value_at_thread_exit(const T& val) |
线程退出时设置结果 | val:要设置的结果值 |
无 | 线程完全退出后才会标记结果就绪 |
void set_exception_at_thread_exit(exception_ptr p) |
线程退出时设置异常 | p:要设置的异常指针 |
无 | 同上 |
3:常用示例
#include <future>
#include <thread>
using namespace std;
void worker(promise<int> pro) {
try {
// 模拟计算
int result = 100 + 200;
pro.set_value(result); // 设置正常结果
} catch (...) {
pro.set_exception(current_exception()); // 设置异常
}
}
int main() {
promise<int> pro;
future<int> fu = pro.get_future(); // 获取对应的future
thread t(worker, move(pro)); // promise不可拷贝,必须移动
cout << fu.get() << endl; // 300
t.join();
return 0;
}
4:坑
- promise 不可拷贝,只能移动:一个共享状态只能有一个生产者
get_future()只能调用一次:第二次调用会抛出std::future_error异常set_value()和set_exception()只能调用一次:重复调用会抛异常- promise 析构前未设置结果:future 调用
get()时会抛出broken_promise异常
4:std::shared_future<T>
1:总述
std::shared_future<T>是可共享的结果句柄,支持多个线程同时读取同一个异步任务的结果。可以从普通future转换而来。
2:核心API接口
与std::future<T>几乎完全相同,只有以下区别:
- 支持拷贝构造和拷贝赋值:多个
shared_future可以指向同一个共享状态 get()可以多次调用:不会消耗结果,所有调用都返回相同的值- 没有
share()方法:本身就是共享的
3:常用示例
#include <future>
#include <thread>
#include <vector>
using namespace std;
int main() {
promise<int> pro;
shared_future<int> sfu = pro.get_future().share(); // 转换为shared_future
vector<thread> threads;
for (int i = 0; i < 3; ++i) {
// 拷贝sfu,多个线程共享同一个结果
threads.emplace_back([sfu]() {
cout << "线程" << this_thread::get_id() << ":" << sfu.get() << endl;
});
}
pro.set_value(42); // 设置结果,所有线程都能读到
for (auto& t : threads) {
t.join();
}
return 0;
}
4:坑
get()返回的是 const 引用:不能修改结果值- 所有
shared_future都析构后,共享状态才会释放 - 线程安全:多个线程同时调用
get()是安全的
5:std::pachaged_task<R (Args...)>
1:总述
std::packaged_task是任务包装器,将任意可调用对象(函数、lambda、仿函数、类成员函数)包装成一个异步任务,自动绑定 promise 和 future,不用手动创建 promise。
2:核心API列表
| 接口 | 作用 | 参数 | 返回值 | 注意事项 |
|---|---|---|---|---|
packaged_task() |
默认构造函数,创建一个无效的 packaged_task | 无 | 无 | 调用任何成员函数都会抛异常 |
template<class F><br>explicit packaged_task(F&& f) |
构造函数,包装可调用对象 | f:要包装的可调用对象 |
无 | 要求f的签名与R(Args...)匹配 |
packaged_task(packaged_task&& other) noexcept |
移动构造函数 | other:要移动的 packaged_task |
无 | 移动后other变为无效 |
packaged_task& operator=(packaged_task&& other) noexcept |
移动赋值运算符 | other:要移动的 packaged_task |
自身引用 | 移动后other变为无效 |
future<R> get_future() |
获取对应的 future 对象 | 无 | 与该任务关联的 future | 1. 只能调用一次2. 调用后 packaged_task 仍然有效 |
void operator()(Args... args) |
执行包装的任务 | args:传递给任务的参数 |
无 | 1. 执行完成后自动设置结果2. 任务抛异常会自动设置到 future 中 |
void make_ready_at_thread_exit(Args... args) |
线程退出时执行任务 | args:传递给任务的参数 |
无 | 线程完全退出后才会标记结果就绪 |
void reset() |
重置任务状态,可重新执行 | 无 | 无 | 1. 只能在任务执行完成后调用2. 会创建新的共享状态 |
bool valid() const noexcept |
检查是否有效 | 无 | true有效,false无效 |
移动或执行后仍然有效,除非被 reset |
3:常用示例
#include <future>
#include <thread>
using namespace std;
int add(int a, int b) {
return a + b;
}
int main() {
// 包装函数,模板参数是函数签名
packaged_task<int(int, int)> task(add);
future<int> fu = task.get_future();
// 启动线程执行任务,传递参数10和20
thread t(move(task), 10, 20); // packaged_task不可拷贝,必须移动
cout << fu.get() << endl; // 30
t.join();
return 0;
}
4:坑
- packaged_task 不可拷贝,只能移动
get_future()只能调用一次operator()只能调用一次:除非先调用reset()重置- 任务执行时自动处理异常:不需要手动调用
set_exception()
6:std::async
1:总述
std::async是最高频使用的接口,自动创建异步任务、自动管理线程、自动绑定 promise 和 future,一行代码就能启动异步任务。
2:核心API列表
// 1. 显式指定启动策略
template<class F, class... Args>
future<result_of_t<F(Args...)>> async(launch policy, F&& f, Args&&... args);
// 2. 默认启动策略(不推荐)
template<class F, class... Args>
future<result_of_t<F(Args...)>> async(F&& f, Args&&... args);
参数说明
policy:启动策略,是std::launch枚举类型,可选值:std::launch::async:强制新开一个线程执行任务std::launch::deferred:延迟执行,调用get()/wait()时才在当前线程执行std::launch::async | std::launch::deferred:系统自选策略(默认值)
f:要执行的可调用对象args:传递给可调用对象的参数
返回值:与任务返回值类型对应的future对象
3:常用示例
#include <future>
#include <iostream>
using namespace std;
int square(int x) {
this_thread::sleep_for(chrono::seconds(1));
return x * x;
}
int main() {
// 显式指定新开线程执行(推荐)
future<int> fu = async(launch::async, square, 10);
cout << "主线程继续执行..." << endl;
cout << "10的平方:" << fu.get() << endl; // 100
return 0;
}
4:坑
- 永远显式指定启动策略:默认策略行为不确定,不同编译器实现不同
- 临时 future 会阻塞主线程:
async(launch::async, func)返回的临时 future 在析构时,会隐式调用join()等待线程结束 launch::deferred任务不调用get()永远不会执行- async 不保证使用线程池:GCC 和 Clang 的实现都是每次创建新线程,不是线程池
// 错误写法:主线程会阻塞1秒
async(launch::async, [](){ this_thread::sleep_for(chrono::seconds(1)); });
cout << "Hello" << endl; // 等上面执行完才打印
7:底层原理(浅显)
1:共享状态
所有 future 组件的底层都依赖共享状态(Shared State),这是一块堆上分配的内存块,是 promise 和 future 之间的通信桥梁。
共享状态里只存 3 样东西:
- 结果数据:异步任务的返回值或异常
- 同步原语:互斥锁 + 条件变量,实现线程安全的等待 - 通知
- 引用计数:原子计数器,管理共享状态的生命周期
2:四大组件和共享状态的关系
- promise:持有共享状态的写权限,负责写入结果
- future:持有共享状态的读权限,负责读取结果
- packaged_task:内部包含一个 promise 和一个可调用对象,执行任务后自动写入结果
- async:内部创建 packaged_task 和 thread,自动执行任务并返回 future
3:核心工作流程
- 创建 promise/packaged_task 时,自动在堆上分配共享状态
- future 通过
get_future()获得共享状态的读权限 - promise 调用
set_value()或 packaged_task 执行完成时:- 将结果写入共享状态
- 标记结果为 "就绪"
- 唤醒所有在
get()/wait()上阻塞的线程
- future 调用
get()时:- 如果结果未就绪,在条件变量上阻塞等待
- 结果就绪后,读取结果并释放共享状态的读权限
4:关键机制
- 为什么
get()会阻塞?:本质是在共享状态内部的条件变量上等待,直到结果就绪被唤醒 - 为什么
get()只能调用一次?:普通 future 的get()会销毁共享状态中的结果,释放读权限 - 为什么 promise 必须移动?:一个共享状态只能有一个写者,避免多个线程同时写入结果
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)