面试中关于 c++ async 的高频面试问题有哪些?

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 的线程池提案,但目前还未标准化)。

注意事项 / 最佳实践

  1. 总是 get()wait()
    • 确保任务完成,避免异常被悄悄忽略。
    • future 析构前未 get() + std::launch::async 时,析构会阻塞等待任务完成。
  2. 避免捕获短生命周期引用
    • 传入 async 的参数会自动按值拷贝(除非手动 std::ref)。保持与 std::thread 一致的规则:生命周期必须覆盖异步执行时间。
  3. 组合多个 future
    • 可用 std::future_status::timeout / wait_for / wait_until 来做超时检测。
    • C++20 引入 wait_all/wait_any 这些更高级的工具,但标准库目前仍然只有 wait_for/wait_until
  4. 不要盲目依赖默认策略
    • 若需要确定新的执行线程,指定 std::launch::async
  5. 性能考虑
    • 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 返回的"结果句柄"或"承诺凭证":

  1. 结果/异常的载体

    • future.get() 阻塞直到异步任务完成,然后返回结果 T
    • 如果任务抛异常,get() 会重新抛出,让调用者在本线程处理。
  2. 同步点

    • get()/wait() 充当"同步栅栏":在此之前主线程与任务并行,调用后确保任务已完成。
  3. 任务生命周期管理

    • 对于 std::launch::asyncfuture 析构前若未 get()/wait(),析构会自动等待任务结束,防止线程"悬挂"。
  4. 状态查询

    • wait_for()/wait_until() 可做超时或轮询。
    • valid() 判断 future 是否仍持有共享状态(get() 一次后就失效)。

简而言之:futureasync 任务与调用者之间的桥梁,负责 拿结果 + 传异常 + 管理完成状态

若没有调用 get()/wait(),则 future 不可以析构,这时候程序会一直卡在这里对吗

不是"不可析构"------future 仍然会析构,但行为取决于启动策略:

  1. std::launch::async

    未调用 get()/wait() 就析构 future 时,析构函数会阻塞等待异步任务结束

    • 如果任务已经完成,析构立刻返回。
    • 如果任务仍在执行,析构会"在这里卡住"直到任务结束。
    • 这样设计是为了防止后台线程"悬挂"或访问已被销毁的资源。
  2. std::launch::deferred

    任务尚未执行时,析构 future 相当于"放弃这次调用"------不会运行任务,也不会阻塞。

  3. 默认策略(async | deferred)

    • 如果实现选择了 async 分支,行为同 1。
    • 如果选择 deferred 且你也没 get(),析构不会阻塞。

所以严格来说:只有 async 分支、且任务尚未完成时,析构会阻塞 。为了避免意外卡住,建议总是显式 get()wait(),把完成时机掌握在自己手里。

future.get()/wait() 也只能显示等待位置,但是不可以让卡住的子线程停止,对吧?

对。future.get() / future.wait() 只是让调用线程在当前点等待任务完成,它们并不会主动终止那个仍在运行的异步任务。换句话说:

  • 若任务正常完成,get()/wait() 就在此处解阻。
  • 若任务卡死或进入无限循环,get()/wait() 只会一直卡住等待,无法"杀掉"子任务线程。

标准 std::future 本身不提供取消或强制停止机制。要支持可控停止,需要:

  1. 设计任务自身可中断 ,周期性检查某个共享标志(如 std::atomic<bool>)并自愿退出。
  2. 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";
}

运行顺序:

  1. std::async 启动异步任务。
  2. 主线程能做别的事。
  3. fut.wait() 阻塞,直到 slow_square 完成。
  4. wait() 返回后任务保证结束,接着调用 get() 取结果(若任务抛异常,此时 get() 才会抛出)。
  5. 如果任务很快完成,wait() 可能立即返回;若任务挂起,wait() 会一直等,但不会中断任务。

面试中关于 c++ async 的高频面试问题有哪些?

C++ std::async 高频面试题(含答案)

  1. std::asyncstd::thread 有什么区别?

    • async 返回 future,自动管理线程生命周期、结果和异常;thread 需要手动 join/detach,无返回值。
    • async 支持 deferred 延迟执行;thread 不行。
    • async 更高层,适合按需获取结果;thread 更底层,适合精细控制。
  2. std::async 的 launch policy 有哪些?默认行为是什么?

    • std::launch::async:立即异步执行(后台线程)。
    • std::launch::deferred:懒执行,直到 future.get()/wait() 时在当前线程运行。
    • 默认 std::launch::async | std::launch::deferred:实现可自行选择;若任务未启动,wait() 必须在调用线程执行它。
  3. future.get() vs future.wait()

    • wait() 只阻塞到任务完成,不获取结果。
    • get() 阻塞并返回结果(或转抛异常),调用一次后 future 失效。
  4. future 析构前未 get()/wait() 会怎样?

    • 若任务以 std::launch::async 启动且未完成,future 析构会阻塞等待任务结束。
    • 若是 deferred,析构不会运行任务。
    • 因此建议总是显式 get()/wait()
  5. std::async 如何传播异常?

    • 若异步函数抛异常,它被捕获到 future;调用 future.get() 时重新抛出。
    • 若从不 get(),异常会悄然被吞掉。
  6. std::async 会创建多少线程?可控吗?

    • std::launch::async 表示必须异步执行,但是"新线程"还是线程池线程由实现决定。
    • std::launch::deferred 不创建线程。
    • 标准未提供线程池个数控制;大量 async 可能导致线程过多。
  7. 捕获引用参数的注意事项?

    • 传递给 async 的参数默认按值复制,与 std::thread 相同。
    • 传引用需 std::ref;确保被引用对象在异步任务完成前一直存在。
  8. std::future_status 是什么?

    • future.wait_for() / wait_until() 返回 future_status::ready / timeout / deferred
    • 可用于轮询或超时等待。
  9. asyncstd::launch::deferred 有哪些坑?

    • 若一直不 get()/wait(),任务根本不会执行。
    • 若希望真正异步,需要显式指定 std::launch::async
  10. std::async 可以取消任务吗?

    • 标准 future 不能直接取消或强制停止任务;需要用户自定义终止信号或使用 C++20 std::jthread + stop_token
相关推荐
彷徨而立2 小时前
【C/C++】什么是 运行时库?运行时库 /MT 和 /MD 的区别?
c语言·c++
qq_417129252 小时前
C++中的桥接模式变体
开发语言·c++·算法
Abona3 小时前
C语言嵌入式全栈Demo
linux·c语言·面试
No0d1es4 小时前
电子学会青少年软件编程(C语言)等级考试试卷(三级)2025年12月
c语言·c++·青少年编程·电子学会·三级
bjxiaxueliang5 小时前
一文掌握C/C++命名规范:风格、规则与实践详解
c语言·开发语言·c++
xu_yule6 小时前
网络和Linux网络-13(高级IO+多路转接)五种IO模型+select编程
linux·网络·c++·select·i/o
2301_765703146 小时前
C++与自动驾驶系统
开发语言·c++·算法
轩情吖6 小时前
Qt的窗口(三)
c++·qt
她说..6 小时前
策略模式+工厂模式实现审批流(面试问答版)
java·后端·spring·面试·springboot·策略模式·javaee