很多人理解 std::future 和 std::promise 时,只停留在“一个线程设置值,一个线程获取值”。
但面试真正想考察的是:为什么需要它们?
本质上,它们解决的是线程间异步结果传递的问题,让一个线程可以安全、简洁地获取另一个线程的计算结果,并且支持异常传递。
如果没有它们,就需要自己维护共享变量、锁和等待机制,代码会变得复杂且容易出错。
没有 future/promise 时,你得怎么干
假设主线程让工作线程计算一个复杂公式,然后拿到结果继续执行。没有 future 的时候,你得自己搭一套传递机制:
int result = 0;
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
int value = heavy_computation();
std::lock_guard<std::mutex> lock(mtx);
result = value;
ready = true;
cv.notify_one();
}
int main() {
std::thread t(worker);
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
std::cout << result << std::endl;
t.join();
}

这段代码没毛病,但写起来很繁琐:你需要维护三个同步原语——mutex、condition_variable、ready 标志——只为了传一个整数。更麻烦的是,如果工作线程抛了异常,主线程根本收不到通知,程序直接 terminate。
我在之前的项目里就遇到过这种情况:后台线程计算某个统计指标,主线程等待结果后更新 UI。当时我用了 mutex + cv 的组合,代码写了三十多行,还因为忘了处理 ready 标志导致了一次空等。那次经历让我印象深刻:线程间传递结果,绝对是个值得单独解决的问题。
future 和 promise 是怎么简化的
C++11 引入的 std::future 和 std::promise 就是为了解决上面的痛点。它们的本质是通过一个共享状态(shared state)在线程间传递结果。promise 是写端,future 是读端,两者通过底层的同步机制协调,你不需要手动管 mutex 和 cv。
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t([&prom] {
int value = heavy_computation();
prom.set_value(value); // 设置结果
});
int result = fut.get(); // 阻塞等待结果
std::cout << result << std::endl;
t.join();

对比两种写法:
| 方式 |
代码量 |
异常处理 |
可读性 |
mutex + cv |
约30行 |
主线程收不到异常 |
繁琐 |
promise + future |
约10行 |
自动传递异常 |
直观 |
future.get() 不仅会阻塞等待结果,还会自动抛出工作线程中的异常。这意味着你不用再写一套异常传递机制,框架已经帮你做好了。
再进一步:async 是最好的封装
如果你觉得 promise + future 还是太繁琐,C++11 还提供了 std::async。它把线程创建和结果传递完全封装起来:
auto fut = std::async(std::launch::async, [] {
return heavy_computation();
});
int result = fut.get(); // 等待结果

这里有两个细节需要注意:
第一,启动策略。std::launch::async 强制创建新线程,而 std::launch::deferred 是延迟执行(调用 get() 时才计算)。如果你不指定策略,编译器可能根据实现自行决定,这在性能调优时是个隐患。
第二,线程生命周期。std::async 返回的 future 在析构时会自动 join 底层线程,不用手动管理。这也是它比手撸 std::thread 安全的原因——你不可能忘记 join。
这两点在面试里很常被问到。有一次面试官问我:"async 和手撸 thread 有什么区别?"我回答了性能差异,但没有提到异常传递和线程生命周期管理,被扣了分。
面试里常见的三个误区
第一个误区,以为 future.get() 可以调多次。
auto fut = std::async(std::launch::async, compute);
int a = fut.get();
int b = fut.get(); // ❌ 崩溃!get() 只能调一次
future 的结果是移动语义的,第二次 get() 会导致未定义行为。如果需要多个线程读取同一个结果,应该用 std::shared_future。
第二个误区,忽略了 set_exception 的作用。
很多人知道 set_value 只能调一次,但不知道还可以 set_exception。当工作线程遇到错误时,传递异常比传递一个特殊值更安全:
std::thread t([&prom] {
try {
prom.set_value(heavy_computation());
} catch (...) {
prom.set_exception(std::current_exception());
}
});
第三个误区,以为 std::async 总是开新线程。
如果你不指定 std::launch::async,编译器可能使用延迟求值策略,这意味着 get() 可能在当前线程同步执行,完全没有并发。写性能敏感代码时,必须显式指定策略。

进阶用法与实际场景
future 不是只能阻塞等待。wait_for 可以让你非阻塞地检查结果是否就绪,这在需要超时控制的场景里很有用:
auto status = fut.wait_for(std::chrono::seconds(2));
if (status == std::future_status::ready) {
// 结果已准备好
} else if (status == std::future_status::timeout) {
// 超时了,做降级处理
}
如果多个线程需要读取同一个结果,用 std::shared_future:
auto fut = std::async(std::launch::async, compute);
std::shared_future<int> sf = fut.share();
// 多个线程可以安全地 sf.get()

在我们的日志系统项目里,曾经用 std::async 封装日志压缩任务:主线程提交日志时,后台异步压缩文件,主线程继续处理业务,等待时间到了再取结果。这种模式比直接阻塞等待压缩完成的方案,吞吐量提升了一倍以上。
当然,future 不是万能的。如果你需要一对多的结果通知,应该用 condition_variable;如果需要连续多次结果,应该用生产者-消费者队列。选对工具比背概念更重要。
总结
future 和 promise 解决的核心问题是:线程间的结果传递。它把低层的 mutex、condition_variable 和异常处理封装起来,让你用几行代码就能安全地获取异步计算结果。
面试里谈到这个话题,别只背概念。把话题引到实际问题上——没有它们时你需要手撰多少行代码、async 怎么简化了线程管理、get() 的语义是什么——面试官会认为你真写过代码。
在 云栈社区,你可以和众多 C++ 开发者深入探讨并发编程的最优实践。