C++
std::future
发布于 2026年7月22日
std::future
std::future 用于表示一个尚未完成、但未来会产生结果或异常的异步操作。它本身不创建线程,而是结果的“接收端”;结果可由 std::async、std::promise 或 std::packaged_task 提供。
可以把 future 理解成一次性结果通道:生产者设置值或异常,消费者等待并通过
get()取走结果。
1. future 的基本模型
异步共享状态通常包含:
- 任务返回值,或任务抛出的异常;
- 结果是否就绪;
- 等待结果所需的同步信息;
- 生产者和消费者之间的生命周期管理。
生产者 消费者
std::async / promise / packaged_task → std::future
设置值或异常 wait / get
常用头文件:
#include <future>
#include <chrono>
#include <thread>
#include <iostream>
std::future<T> 表示未来会得到一个 T;std::future<void> 表示任务只报告完成或异常。
2. 使用 std::async 创建异步任务
最简单的用法:
#include <future>
#include <iostream>
int calculate(int x)
{
return x * x;
}
int main()
{
std::future<int> result =
std::async(std::launch::async, calculate, 12);
// 主线程可继续做其他工作
std::cout << result.get() << '\n'; // 144
}
get() 会等待任务完成,然后返回结果。
传递 lambda
auto future = std::async(std::launch::async, [] {
return std::string("done");
});
std::string value = future.get();
传递成员函数
class Worker {
public:
int run(int value)
{
return value + 1;
}
};
Worker worker;
auto future = std::async(
std::launch::async,
&Worker::run,
&worker,
41
);
std::cout << future.get(); // 42
必须保证 worker 在异步任务结束前仍然存活。更安全的做法可能是按值传递对象、使用 shared_ptr,或者明确等待任务完成。
3. async 的启动策略
std::async 有两种主要策略。
std::launch::async
auto f = std::async(std::launch::async, task);
任务异步执行。实现通常创建线程或使用内部执行资源。
std::launch::deferred
auto f = std::async(std::launch::deferred, task);
任务不会立刻执行,而是在第一次调用 get() 或 wait() 的线程中同步执行。
默认策略
auto f = std::async(task);
默认相当于允许实现从 async | deferred 中选择。因此,“调用 async 就一定创建后台线程”是错误的。
如果确实要求并发执行,应显式指定:
std::async(std::launch::async, task);
4. get:获取结果或重新抛出异常
int value = future.get();
get() 有三个重要行为:
- 如果结果尚未就绪,则阻塞等待;
- 如果任务成功,则返回结果;
- 如果任务抛出异常,则在调用
get()的线程中重新抛出。
auto future = std::async(std::launch::async, []() -> int {
throw std::runtime_error("calculation failed");
});
try {
int value = future.get();
} catch (const std::exception& e) {
std::cerr << e.what();
}
get 只能调用一次
std::future 是一次性消费者。调用 get() 后,future 不再关联共享状态:
int value = future.get();
int again = future.get(); // 抛 std::future_error
如果需要多个消费者读取同一个结果,应使用 std::shared_future。
5. valid:检查 future 是否关联共享状态
if (future.valid()) {
int value = future.get();
}
以下情况通常会使原 future 失效:
- 调用过
get(); - future 被移动;
- 默认构造但尚未接收共享状态。
std::future<int> f1 =
std::async(std::launch::async, [] { return 42; });
std::future<int> f2 = std::move(f1);
std::cout << f1.valid(); // false
std::cout << f2.valid(); // true
valid() 只表示是否关联共享状态,不表示任务已经完成。
6. wait、wait_for 与 wait_until
wait
future.wait();
阻塞到结果就绪,但不取走结果,也不会在此处重新抛出任务异常。异常仍由后续 get() 抛出。
wait_for
using namespace std::chrono_literals;
auto status = future.wait_for(100ms);
可能返回:
std::future_status::ready:结果已就绪;std::future_status::timeout:超时;std::future_status::deferred:任务使用延迟执行策略。
switch (future.wait_for(100ms)) {
case std::future_status::ready:
std::cout << future.get();
break;
case std::future_status::timeout:
std::cout << "still running";
break;
case std::future_status::deferred:
std::cout << "will run on wait/get";
break;
}
wait_until
auto deadline =
std::chrono::steady_clock::now() +
std::chrono::seconds(1);
if (future.wait_until(deadline) ==
std::future_status::ready) {
std::cout << future.get();
}
超时等待不会取消任务。它只表示调用者不再继续等待。
7. future 析构时为什么可能阻塞
由 std::async(std::launch::async, ...) 返回的 future,在特定条件下析构可能等待异步任务结束:
std::async(std::launch::async, [] {
long_running_task();
}); // 临时 future 在语句末尾析构,可能在这里等待
这会让看似异步的代码表现得像同步代码。
正确做法是保存 future,并明确决定何时等待:
auto future = std::async(
std::launch::async,
long_running_task
);
// 其他工作
future.get();
future 不是“分离线程”的工具。需要可控线程生命周期时,应考虑
std::jthread、线程池或任务执行器。
8. std::promise:手动提供结果
std::promise<T> 是共享状态的生产端,std::future<T> 是消费端。
#include <future>
#include <thread>
void producer(std::promise<int> promise)
{
try {
int result = 42;
promise.set_value(result);
} catch (...) {
promise.set_exception(
std::current_exception()
);
}
}
int main()
{
std::promise<int> promise;
std::future<int> future = promise.get_future();
std::thread worker(
producer,
std::move(promise)
);
std::cout << future.get();
worker.join();
}
设置异常
try {
perform_work();
promise.set_value(result);
} catch (...) {
promise.set_exception(std::current_exception());
}
消费者调用 future.get() 时会重新抛出该异常。
broken_promise
如果 promise 在没有设置值或异常的情况下被销毁,future 会就绪,但 get() 会抛出 std::future_error,错误条件为 broken_promise。
std::future<int> make_broken_future()
{
std::promise<int> promise;
auto future = promise.get_future();
return future; // promise 被销毁但未设置结果
}
9. promise 的常见限制
- 每个 promise 只能调用一次
get_future(); set_value()或set_exception()只能成功设置一次;- promise 不可复制,只能移动;
- 必须保证所有成功路径和异常路径最终都会设置结果;
- promise 被移动后,原对象不再拥有共享状态。
set_value_at_thread_exit
promise.set_value_at_thread_exit(value);
值会在线程即将退出时才变为就绪。对应的异常接口是 set_exception_at_thread_exit。
10. std::packaged_task:把可调用对象包装成任务
std::packaged_task<R(Args...)> 包装一个可调用对象,并把返回值或异常自动写入关联共享状态。
std::packaged_task<int(int, int)> task(
[](int a, int b) {
return a + b;
}
);
std::future<int> future = task.get_future();
std::thread worker(std::move(task), 20, 22);
std::cout << future.get(); // 42
worker.join();
它常用于线程池:队列中保存任务,提交者持有 future。
template<class F>
auto submit(F&& function)
{
using Result = std::invoke_result_t<F>;
auto task = std::make_shared<
std::packaged_task<Result()>
>(std::forward<F>(function));
std::future<Result> future =
task->get_future();
enqueue([task] {
(*task)();
});
return future;
}
注意事项
- packaged_task 只能移动,不能复制;
- 执行任务后结果才会就绪;
- 如果任务从未执行且对象被销毁,消费者会收到 broken_promise;
- 默认构造或被移动后的 task 没有有效共享状态;
reset()可为下一次调用创建新的共享状态,但旧 future 仍关联旧状态。
11. shared_future:让多个消费者共享结果
普通 future 的 get() 只能调用一次。std::shared_future 可复制,并允许多个线程等待和读取同一共享状态。
std::future<int> future =
std::async(std::launch::async, [] {
return 42;
});
std::shared_future<int> shared =
future.share();
auto reader = [shared] {
std::cout << shared.get() << '\n';
};
std::thread t1(reader);
std::thread t2(reader);
t1.join();
t2.join();
也可以直接构造:
std::shared_future<int> shared =
std::async(std::launch::async, task);
注意事项
future.share()会把共享状态转移给 shared_future,原 future 随后失效;- shared_future 对象可安全地在多个线程中分别访问;
- 如果多个线程操作的是同一个 shared_future 对象本身,仍应避免并发修改;
- 对于
shared_future<T>,get()返回const T&,要注意引用生命周期和对象是否可安全共享; - shared_future 共享结果,不等于结果对象自身是线程安全的。
12. future
当任务没有返回值时使用 future<void>:
std::future<void> future =
std::async(std::launch::async, [] {
perform_work();
});
future.get();
这里 get() 不返回数据,但仍会等待完成并传播异常。因此不要因为任务返回 void 就省略 get。
13. 参数传递与引用陷阱
std::async、线程和 packaged_task 通常会衰减复制参数。
按值传递:
std::string text = "hello";
auto future = std::async(
std::launch::async,
[](std::string value) {
return value.size();
},
text
);
显式按引用传递:
int value = 0;
auto future = std::async(
std::launch::async,
[](int& x) {
x = 42;
},
std::ref(value)
);
future.get();
使用 std::ref 时必须保证被引用对象在任务完成前仍然存在,并确保并发访问受到正确同步。
14. future 不提供任务取消
标准 std::future 没有 cancel()。wait_for 超时也不会停止任务。
可使用协作式取消:
#include <stop_token>
#include <thread>
std::jthread worker([](std::stop_token token) {
while (!token.stop_requested()) {
do_one_step();
}
});
// 稍后
worker.request_stop();
如果必须把结果与取消结合,可让任务同时接收 stop_token,并通过 promise/future 返回结果。
取消是一种协议:任务必须定期检查请求,并在安全点退出。
15. 并行组合的局限
标准 future 没有通用的 then、when_all、when_any 接口。多个 future 常见的简单等待方式:
auto f1 = std::async(
std::launch::async,
task1
);
auto f2 = std::async(
std::launch::async,
task2
);
auto r1 = f1.get();
auto r2 = f2.get();
大量任务不应无节制地调用 std::async(std::launch::async, ...),否则可能创建过多线程。更适合使用:
- 有界线程池;
- 任务调度器;
- 支持 continuation 的并发库;
- C++ 协程与合适的异步运行时。
16. 内存可见性与同步保证
当生产者把共享状态设置为 ready,等待该状态的 future 操作会建立必要的同步,使消费者能看到任务在完成前产生的结果。
这意味着:
int data = 0;
auto future = std::async(
std::launch::async,
[&] {
data = 42;
}
);
future.get();
std::cout << data; // 任务已完成,读取安全
但如果主线程在 get() 前同时读取 data,仍可能发生数据竞争。future 只在正确的等待或结果获取边界上提供同步,不会自动保护所有共享访问。
17. 常见错误
忘记调用 get,导致异常被忽略
auto future = std::async(
std::launch::async,
[] {
throw std::runtime_error("failed");
}
);
// 没有 future.get(),异常没有在调用线程中被观察
误以为默认 async 一定并发
auto future = std::async(task);
实现可能选择 deferred。要求并发时应显式使用 std::launch::async。
对同一个 future 调用两次 get
第一次 get 后 future 已失效。需要多次读取时使用 shared_future。
忽略对象生命周期
异步任务捕获 this、引用或裸指针时,对象可能在任务完成前销毁。
auto future = std::async(
std::launch::async,
[this] {
use_members();
}
);
应明确等待任务,或使用合适的所有权模型。
把超时当成取消
wait_for 返回 timeout 后,后台任务可能仍在运行并访问资源。
持锁调用 get
std::lock_guard lock(m);
future.get();
如果异步任务也需要 m,就会死锁。一般不要在持锁时等待 future。
18. 如何选择创建方式
| 需求 | 推荐方式 |
|---|---|
| 简单执行函数并获取结果 | std::async |
| 手动控制何时设置值或异常 | std::promise |
| 把任务放入队列或线程池 | std::packaged_task |
| 多个消费者读取同一结果 | std::shared_future |
| 需要可取消线程任务 | std::jthread + stop_token |
| 大规模任务调度与组合 | 线程池、协程或专用异步运行时 |
19. 实用检查清单
- 是否明确指定了所需的 async 启动策略?
- 是否最终调用了 get,从而观察异常?
- 是否避免对同一 future 调用多次 get?
- 捕获的引用、裸指针和 this 是否存活足够久?
- 是否误把 wait_for 超时当成任务取消?
- 是否可能在持锁时调用 wait 或 get?
- promise 的所有路径是否都会设置值或异常?
- packaged_task 是否一定会被执行?
- 多消费者场景是否应该使用 shared_future?
- 大量任务是否应该交给有界线程池?
- 是否明确管理了任务、共享状态和相关对象的生命周期?
20. 总结
future是异步结果的接收端,而不是线程本身;async适合简单异步调用,但要留意启动策略和析构等待;promise允许手动交付值或异常;packaged_task适合把可调用对象接入任务队列;shared_future适合多个消费者;get()同时承担等待、取值和异常传播;- 超时不等于取消,future 也不会自动解决对象生命周期和数据竞争问题。