std::async 总览
- 头文件:
#include <future> - 作用:把可调用对象(函数、lambda、成员函数...)交给运行时异步执行,返回一个
std::future<T>供你日后get()结果。 - 等价于"我想把这段代码扔到后台执行,并能在需要时拿到结果/异常"。
函数签名与返回值
cpp
template <class F, class... Args>
std::future<std::invoke_result_t<F, Args...>>
async(std::launch policy, F&& f, Args&&... args);
future<T>:代表一个异步任务的结果。future.get()会阻塞直到任务完成,返回结果或抛出任务里的异常。future.valid()判断 future 是否仍持有共享状态(get()一次后就无效)。
std::invoke_result_t<F, Args...>:自动推导返回类型。
Launch Policy(启动策略)
1. std::launch::async
- 强制异步:立刻在新线程(或线程池)中运行。
future.get()等待该线程结束。- 适合 CPU 密集、长耗时任务。
2. std::launch::deferred
- 延迟执行 :并不真正开启新线程,直到你调用
future.get()或future.wait()时才执行,且在调用线程上运行。 - 类似"懒计算"。
3. 默认策略
std::async(f, args...)不传 policy 时,相当于std::launch::async | std::launch::deferred。- 实现可以自行决定是立即异步执行还是延迟。
- 一旦
future.wait()被调用,若任务尚未启动,就必须在调用线程执行。
如何选择?
- 想要确定异步线程 → 明确指定
std::launch::async - 希望只在需要时计算、且不想新线程 → 用
std::launch::deferred - 不介意交给实现决定 → 默认即可
与 std::thread 的区别
std::thread |
std::async |
|---|---|
| 手动管理线程生命周期(要 join 或 detach) | RAII:future 析构前自动管理。如果 future 在析构前没等任务完成,默认会在析构时阻塞等待(async policy)。 |
| 无返回值(只能靠共享数据/引用) | future<T> + get() 拿结果,异常自动传播 |
| 无法延迟执行 | 可 deferred |
| 更直接、更低层 | 更高层,含同步 & 结果传递 |
异常传播
- 若被调用的函数抛异常,
future.get()会重新抛出同一个异常。 - 如果不调用
get(),异常也不会立刻报错------这也是为何应该在 future 销毁前get()或wait(),避免默默吞掉异常。
典型用法示例
cpp
#include <future>
#include <iostream>
#include <thread>
int slow_add(int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return a + b;
}
int main() {
auto fut = std::async(std::launch::async, slow_add, 3, 4);
// 主线程可以去做别的事...
int result = fut.get(); // 阻塞等待,等 slow_add 完成
std::cout << "result = " << result << "\n";
}
使用 deferred
cpp
auto fut = std::async(std::launch::deferred, slow_add, 3, 4);
// 没有真正开线程,直到:
int result = fut.get(); // 此时才执行 slow_add
std::async 不等于线程池
- 标准库并未强制
std::async使用线程池,它可以每次创建线程,也可以复用(实现自定)。 - 如果需要稳定可控的线程池,需要自己实现或用第三方库(或 C++23
std::execution的线程池提案,但目前还未标准化)。
注意事项 / 最佳实践
- 总是
get()或wait()- 确保任务完成,避免异常被悄悄忽略。
- future 析构前未
get()+std::launch::async时,析构会阻塞等待任务完成。
- 避免捕获短生命周期引用
- 传入
async的参数会自动按值拷贝(除非手动std::ref)。保持与std::thread一致的规则:生命周期必须覆盖异步执行时间。
- 传入
- 组合多个
future- 可用
std::future_status::timeout/wait_for/wait_until来做超时检测。 - C++20 引入
wait_all/wait_any这些更高级的工具,但标准库目前仍然只有wait_for/wait_until。
- 可用
- 不要盲目依赖默认策略
- 若需要确定新的执行线程,指定
std::launch::async。
- 若需要确定新的执行线程,指定
- 性能考虑
async每次都可能创建线程,频繁调用会有开销。- 对于大量小任务,考虑自建线程池或使用任务调度器。
小结
std::async是一个把函数调用包装成异步任务的高层接口,配套std::future自动管理生命周期、返回值和异常。- 通过 launch policy 控制执行方式(异步线程 vs 延迟)。
- 异常和返回值都会通过 future 传播。
- 使用它可以减少手动管理线程的繁琐,更安全地传递结果。
std::async:(std::launch::deferred, slow_add, 3, 4) 和 std::async(std::launch::async, slow_add, 3, 4) 都一定会开启新的线程吗?
不会。
std::async(std::launch::deferred, slow_add, 3, 4):一定不会 立即创建线程。调用get()或wait()时,任务才会在调用者线程里运行。std::async(std::launch::async, slow_add, 3, 4):标准要求必须异步执行,即使用后台线程(实现可以是新线程或线程池里的线程,但一定不是调用者线程同步执行)。
总结:deferred 不开线程,async 会开(或复用)后台线程。
使用 std::async 时,future 起到了什么作用?它是一个什么样的角色?
std::future<T> 是 std::async 返回的"结果句柄"或"承诺凭证":
-
结果/异常的载体
future.get()阻塞直到异步任务完成,然后返回结果T。- 如果任务抛异常,
get()会重新抛出,让调用者在本线程处理。
-
同步点
get()/wait()充当"同步栅栏":在此之前主线程与任务并行,调用后确保任务已完成。
-
任务生命周期管理
- 对于
std::launch::async,future析构前若未get()/wait(),析构会自动等待任务结束,防止线程"悬挂"。
- 对于
-
状态查询
wait_for()/wait_until()可做超时或轮询。valid()判断future是否仍持有共享状态(get()一次后就失效)。
简而言之:future 是 async 任务与调用者之间的桥梁,负责 拿结果 + 传异常 + 管理完成状态。
若没有调用 get()/wait(),则 future 不可以析构,这时候程序会一直卡在这里对吗
不是"不可析构"------future 仍然会析构,但行为取决于启动策略:
-
std::launch::async未调用
get()/wait()就析构future时,析构函数会阻塞等待异步任务结束。- 如果任务已经完成,析构立刻返回。
- 如果任务仍在执行,析构会"在这里卡住"直到任务结束。
- 这样设计是为了防止后台线程"悬挂"或访问已被销毁的资源。
-
std::launch::deferred任务尚未执行时,析构
future相当于"放弃这次调用"------不会运行任务,也不会阻塞。 -
默认策略(async | deferred)
- 如果实现选择了 async 分支,行为同 1。
- 如果选择 deferred 且你也没
get(),析构不会阻塞。
所以严格来说:只有 async 分支、且任务尚未完成时,析构会阻塞 。为了避免意外卡住,建议总是显式 get() 或 wait(),把完成时机掌握在自己手里。
future.get()/wait() 也只能显示等待位置,但是不可以让卡住的子线程停止,对吧?
对。future.get() / future.wait() 只是让调用线程在当前点等待任务完成,它们并不会主动终止那个仍在运行的异步任务。换句话说:
- 若任务正常完成,
get()/wait()就在此处解阻。 - 若任务卡死或进入无限循环,
get()/wait()只会一直卡住等待,无法"杀掉"子任务线程。
标准 std::future 本身不提供取消或强制停止机制。要支持可控停止,需要:
- 设计任务自身可中断 ,周期性检查某个共享标志(如
std::atomic<bool>)并自愿退出。 - C++20 起可以考虑
std::jthread+stop_token(能显式请求停止,但仍需任务配合检查)。
总结:future 负责"等待结果",不能"终止任务"。
请示例future.wait 的用法?
future.wait() 让调用线程在"某个点"阻塞,直到对应的异步任务完成,但不取结果(get() 仍可在之后调用)。示例:
cpp
#include <future>
#include <iostream>
#include <thread>
#include <chrono>
int slow_square(int x) {
std::this_thread::sleep_for(std::chrono::seconds(2));
return x * x;
}
int main() {
std::future<int> fut = std::async(std::launch::async, slow_square, 5);
std::cout << "main: do other work...\n";
// 阻塞等待任务结束
fut.wait();
std::cout << "main: task finished\n";
// 任务完成后再取结果(仍然需要 get)
int result = fut.get();
std::cout << "result = " << result << "\n";
}
运行顺序:
std::async启动异步任务。- 主线程能做别的事。
fut.wait()阻塞,直到slow_square完成。wait()返回后任务保证结束,接着调用get()取结果(若任务抛异常,此时get()才会抛出)。- 如果任务很快完成,
wait()可能立即返回;若任务挂起,wait()会一直等,但不会中断任务。
面试中关于 c++ async 的高频面试问题有哪些?
C++ std::async 高频面试题(含答案)
-
std::async和std::thread有什么区别?async返回future,自动管理线程生命周期、结果和异常;thread需要手动join/detach,无返回值。async支持deferred延迟执行;thread不行。async更高层,适合按需获取结果;thread更底层,适合精细控制。
-
std::async的 launch policy 有哪些?默认行为是什么?std::launch::async:立即异步执行(后台线程)。std::launch::deferred:懒执行,直到future.get()/wait()时在当前线程运行。- 默认
std::launch::async | std::launch::deferred:实现可自行选择;若任务未启动,wait()必须在调用线程执行它。
-
future.get()vsfuture.wait()?wait()只阻塞到任务完成,不获取结果。get()阻塞并返回结果(或转抛异常),调用一次后future失效。
-
若
future析构前未get()/wait()会怎样?- 若任务以
std::launch::async启动且未完成,future析构会阻塞等待任务结束。 - 若是
deferred,析构不会运行任务。 - 因此建议总是显式
get()/wait()。
- 若任务以
-
std::async如何传播异常?- 若异步函数抛异常,它被捕获到
future;调用future.get()时重新抛出。 - 若从不
get(),异常会悄然被吞掉。
- 若异步函数抛异常,它被捕获到
-
std::async会创建多少线程?可控吗?std::launch::async表示必须异步执行,但是"新线程"还是线程池线程由实现决定。std::launch::deferred不创建线程。- 标准未提供线程池个数控制;大量
async可能导致线程过多。
-
捕获引用参数的注意事项?
- 传递给
async的参数默认按值复制,与std::thread相同。 - 传引用需
std::ref;确保被引用对象在异步任务完成前一直存在。
- 传递给
-
std::future_status是什么?future.wait_for()/wait_until()返回future_status::ready/timeout/deferred。- 可用于轮询或超时等待。
-
async中std::launch::deferred有哪些坑?- 若一直不
get()/wait(),任务根本不会执行。 - 若希望真正异步,需要显式指定
std::launch::async。
- 若一直不
-
std::async可以取消任务吗?- 标准
future不能直接取消或强制停止任务;需要用户自定义终止信号或使用 C++20std::jthread + stop_token。
- 标准