C++ 异步任务与线程生命周期:`std::async`、`future`、`thread`、`jthread` 与协作式退出

面向已有 C++ 多线程基础的工程实践笔记

重点:任务结果、线程生命周期、join()、request_stop()、stop_token 与协作式取消

\[wxWidgets 与 Boost.Asio 线程协作:future_promise 的适用场景与正确用法\]


1. 文档目标

本文整理以下几个容易混淆但非常关键的 C++ 并发概念:

  • std::promise / std::future 的结果传递模型
  • std::async 的作用与执行策略
  • std::async 返回临时 future 时为什么会"意外阻塞"
  • 不调用 future::get() 会发生什么
  • std::thread 为什么不能直接带着 joinable() 状态析构
  • std::terminate() 终止的是线程还是整个进程
  • std::jthread 相比 std::thread 改进了什么
  • std::stop_token 与传统 std::atomic_bool stop 的本质区别
  • request_stop() 到底做了什么
  • request_stop() 后是否一定要立即 join()
  • join() 的本质是什么
  • 如何为设备 IO、后台线程、Boost.Asio 等工程代码设计可靠退出流程

本文特别强调一个核心思想:

线程"请求退出"、线程"真正结束"、调用方"确认线程已经结束"是三个不同的动作。


2. 从 promise/future 到 std::async

2.1 promise/future 的基本模型

\[wxWidgets 与 Boost.Asio 线程协作:future_promise 的适用场景与正确用法\]

std::promise<T> 与 std::future<T> 可以理解为:

text 复制代码
std::promise<T>
    = 结果生产者

std::future<T>
    = 结果消费者

典型流程:

text 复制代码
线程 A
    │
    │ 创建 promise / future
    │
    ├─────────────────────────────┐
    │                             │
    │                         线程 B
    │                             │
    │                        执行任务
    │                             │
    │                    promise.set_value()
    │                             │
    │<────────────────────────────┘
    │
future.get()
    │
    ▼
取得结果

例如:

cpp 复制代码
std::promise<int> p;
std::future<int> f = p.get_future();

std::thread worker([p = std::move(p)]() mutable {
    p.set_value(123);
});

int value = f.get();

worker.join();

2.2 std::async 是更高层的任务封装

如果任务本身就是:

text 复制代码
执行一个函数
    ↓
得到一个结果

那么手工创建:

text 复制代码
thread
+
promise
+
future
+
异常传递

往往比较繁琐。

std::async 可以直接:

cpp 复制代码
auto f = std::async(
    std::launch::async,
    [] {
        return 123;
    }
);

int result = f.get();

可以把它理解为:

text 复制代码
std::async()
    │
    ├── 安排 Callable 执行
    ├── 建立 shared state
    ├── 保存返回值
    ├── 保存异常
    └── 返回 future

所以:

text 复制代码
std::thread
    更关注"线程"

std::async
    更关注"任务 + 结果"

这是从线程级抽象向任务级抽象的提升。


3. std::async 的 shared state

std::async、std::promise、std::future 背后都有一个非常关键的概念:

text 复制代码
shared state

可以概念化为:

text 复制代码
                 shared state
              ┌─────────────────┐
              │ result          │
              │ exception       │
              │ ready flag      │
              │ synchronization │
              └────────▲────────┘
                       │
           ┌───────────┴───────────┐
           │                       │
      async task                future
           │                       │
      return / throw              get

例如:

cpp 复制代码
auto f = std::async(
    std::launch::async,
    [] {
        return 123;
    }
);

任务完成后:

text 复制代码
return 123
    ↓
shared state
    ↓
future.get()
    ↓
123

如果任务抛异常:

text 复制代码
throw exception
    ↓
shared state 保存 exception
    ↓
future.get()
    ↓
重新抛出 exception

4. std::async 的执行策略

4.1 std::launch::async

cpp 复制代码
auto f = std::async(
    std::launch::async,
    work
);

语义:

安排 work 在另一个执行线程中运行。

典型时间线:

text 复制代码
调用线程                         async worker

std::async()
    │
    ├──────────────────────────> work()
    │                              ███████████
    │
继续执行其它代码
████████
    │
f.get()
    │
    │ 等待剩余任务
    ▼

4.2 std::launch::deferred

cpp 复制代码
auto f = std::async(
    std::launch::deferred,
    work
);

此时 work() 并不会立即执行。

概念上:

text 复制代码
std::async()
    ↓
只保存任务
    ↓
暂时不执行
    ↓
future.get() / wait()
    ↓
此时才执行 work()

因此:

cpp 复制代码
auto f = std::async(
    std::launch::deferred,
    work
);

更接近:

text 复制代码
lazy evaluation
延迟求值

而不是典型的后台并行。


4.3 不指定策略时要谨慎

cpp 复制代码
auto f = std::async(work);

默认允许实现选择:

text 复制代码
std::launch::async
或
std::launch::deferred

如果工程上明确要求并行执行,更推荐:

cpp 复制代码
auto f = std::async(
    std::launch::async,
    work
);

5. 关键坑点:不保存 std::async 返回的 future

这是本次讨论中非常重要的一个点。

5.1 错误直觉

很多人第一次看到:

cpp 复制代码
std::async(
    std::launch::async,
    work
);

do_other_work();

会认为:

text 复制代码
启动 work()
    ↓
立即继续
    ↓
do_other_work()

也就是:

text 复制代码
work             ███████████
do_other_work      ███████

但这通常不是实际效果。


5.2 真正发生的事情

std::async() 会返回一个 std::future。

如果没有变量接收:

cpp 复制代码
std::async(
    std::launch::async,
    work
);

那么产生的是一个临时 future。

可以概念化成:

cpp 复制代码
{
    std::future<void> temporary =
        std::async(
            std::launch::async,
            work
        );

} // 当前 full-expression 结束,temporary 析构

对于 std::launch::async 创建的异步任务,最后一个关联 future 的析构在必要时会等待任务结束。

因此:

text 复制代码
调用线程                         async worker

std::async()
    │
    ├──────────────────────────> work()
    │                              █████████████
    │
临时 future 析构
    │
    │ 等待..........................
    │<──────────────────────────── 完成
    │
do_other_work()
████████████

最终变成近似串行:

text 复制代码
work           █████████████
do_other_work               ████████

5.3 正确做法

如果希望真正形成并发区间:

cpp 复制代码
auto f = std::async(
    std::launch::async,
    work
);

do_other_work();

f.get();

这样:

text 复制代码
调用线程                         async worker

std::async()
    │
    ├──────────────────────────> work()
    │                              █████████████
    │
do_other_work()
██████████
    │
f.get()
    │
    │ 必要时等待
    ▼

核心结论:

如果希望 std::async(std::launch::async, ...) 与调用线程后续代码真正重叠执行,应保存其返回的 future。


6. 不调用 future::get() 会发生什么

这是另一个需要明确区分的点。

cpp 复制代码
auto f = std::async(
    std::launch::async,
    work
);

// 没有 f.get()

并不表示:

text 复制代码
future 不 get
    ↓
worker 被杀掉

不是。

任务仍然会正常运行。


6.1 get() 不是用于"维持线程生命周期"

future::get() 的主要作用是:

text 复制代码
等待结果 ready
+
取得结果
+
如果任务抛异常则重新抛出
+
使普通 future 失效

如果一直没有 get():

text 复制代码
async task
    ↓
正常运行
    ↓
结果 / 异常进入 shared state
    ↓
future 最终析构

6.2 不 get() 的主要问题一:结果被丢弃

cpp 复制代码
auto f = std::async(
    std::launch::async,
    [] {
        return 123;
    }
);

如果从不:

cpp 复制代码
f.get();

则:

text 复制代码
123
    ↓
shared state
    ↓
无人读取
    ↓
最终销毁

6.3 不 get() 的主要问题二:异常可能被静默丢弃

cpp 复制代码
auto f = std::async(
    std::launch::async,
    [] {
        throw std::runtime_error("failed");
    }
);

异常进入 shared state。

正常应:

cpp 复制代码
try {
    f.get();
}
catch (const std::exception& e) {
    // handle error
}

如果从不 get():

text 复制代码
worker throw
    ↓
shared state 保存异常
    ↓
没人读取
    ↓
future / state 销毁
    ↓
错误可能被业务逻辑完全忽略

所以:

不 get() 通常不是线程生命周期错误,但可能是业务逻辑上的坏味道。


7. std::thread 对象与实际执行线程不是同一个东西

这是理解 join()、detach()、析构行为的基础。

text 复制代码
std::thread 对象
        ≠
操作系统里的执行线程

可以把:

cpp 复制代码
std::thread t(work);

理解成:

text 复制代码
std::thread t
     │
     │ 管理 / 关联
     ▼
OS execution thread

std::thread 更像一个 C++ 层面的线程句柄和生命周期管理对象。


8. 为什么 std::thread 不 join() 会出问题

8.1 std::thread 析构并不会自动结束线程

例如:

cpp 复制代码
void foo()
{
    std::thread t([] {
        do_work();
    });

}

很多人的直觉是:

text 复制代码
foo 返回
    ↓
t 析构
    ↓
线程一起被销毁

这是错误的。

如果:

cpp 复制代码
t.joinable() == true

而 t 开始析构:

text 复制代码
t.~thread()
    ↓
发现 joinable == true
    ↓
std::terminate()

9. std::terminate() 终止的是谁

这个点必须非常明确。

text 复制代码
std::terminate()

不是:

text 复制代码
终止这个 worker thread

而是:

进入整个 C++ 程序的不可恢复终止流程,通常最终导致整个进程结束。

因此:

text 复制代码
thread 析构
    ↓
joinable == true
    ↓
std::terminate()
    ↓
整个进程终止

不是:

text 复制代码
只把 t 对应的线程杀掉

10. 即使线程函数已经执行完,也仍可能需要 join()

这是很容易误解的一点。

cpp 复制代码
std::thread t([] {
    // 很快执行完
});

std::this_thread::sleep_for(
    std::chrono::seconds(5)
);

5 秒后 worker 肯定已经执行完。

但是:

cpp 复制代码
t.joinable();

通常仍然是:

cpp 复制代码
true

原因:

text 复制代码
线程函数已经结束

和:

text 复制代码
std::thread 已完成生命周期收尾

是两回事。

因此仍然需要:

cpp 复制代码
t.join();

之后:

cpp 复制代码
t.joinable() == false;

11. join() 的本质

这是本次讨论中另一个核心问题。

一句话:

join() 的本质是:等待目标线程结束,并完成线程关联状态的收尾。

它不是:

text 复制代码
请求线程退出
杀死线程
暂停线程

11.1 第一层作用:等待线程结束

cpp 复制代码
std::thread t([] {
    do_work();
});

t.join();

do_next();

执行:

text 复制代码
当前线程                         worker

创建 t
    │
    ├──────────────────────────> do_work()
    │                              █████████
    │
t.join()
    │
    │ 等待..........................
    │<──────────────────────────── return
    │
join 返回
    │
do_next()

因此:

cpp 复制代码
t.join();

返回以后可以确定:

目标线程已经结束。


11.2 第二层作用:解除 std::thread 与执行线程的关联

创建:

cpp 复制代码
std::thread t(work);

t.joinable(); // true

执行:

cpp 复制代码
t.join();

之后:

cpp 复制代码
t.joinable(); // false

即:

text 复制代码
join 前

std::thread
     │
     ▼
execution thread
joinable = true


join 后

std::thread

不再关联执行线程
joinable = false

11.3 第三层作用:建立线程同步关系

例如:

cpp 复制代码
int result = 0;

std::thread t([&] {
    result = 123;
});

t.join();

std::cout << result;

这是安全的。

因为线程结束与成功 join() 返回之间建立了同步关系。

可以理解为:

text 复制代码
worker

result = 123
    ↓
线程结束
    ↓
happens-before
    ↓
join() 返回
    ↓
main 读取 result

因此 join() 不只是"等一下"。

它还是一个明确的:

text 复制代码
线程同步点

12. detach() 是什么

cpp 复制代码
std::thread t(work);

t.detach();

含义:

std::thread 对象放弃继续管理这个执行线程。

之后:

cpp 复制代码
t.joinable() == false;

但真正的线程可能继续运行:

text 复制代码
调用线程                         detached worker

thread(...)
    │
    ├──────────────────────────> work()
    │                              █████████████
    │
detach()
    │
foo 返回
                                   █████████████
                                   继续执行

注意:

detach() 不是结束线程,而是放弃管理线程。


12.1 detach() 的典型风险:悬空引用

cpp 复制代码
void foo()
{
    int value = 123;

    std::thread t([&] {
        std::this_thread::sleep_for(
            std::chrono::seconds(1)
        );

        std::cout << value;
    });

    t.detach();
}

foo() 返回后:

text 复制代码
value 销毁

但 detached worker 之后仍访问:

cpp 复制代码
value

造成悬空引用与 Undefined Behavior。

因此工程代码中通常不建议随意 detach()。


13. std::jthread:C++20 对线程生命周期的改进

std::jthread 可以看成:

text 复制代码
joining thread

它相比 std::thread 有两个重要改进:

text 复制代码
1. 析构时自动处理 join
2. 支持标准化的协作式停止

例如:

cpp 复制代码
void worker(std::stop_token st)
{
    while (!st.stop_requested()) {
        do_work();
    }
}

void foo()
{
    std::jthread t(worker);

}

当 t 析构时,如果仍然 joinable(),大致行为是:

cpp 复制代码
request_stop();
join();

14. std::stop_token 的核心:不是"杀线程"

std::stop_token 最重要的理解是:

它不是线程终止机制,而是停止请求的观察机制。

例如:

cpp 复制代码
void worker(std::stop_token st)
{
    while (!st.stop_requested()) {
        do_work();
    }

    cleanup();
}

控制线程:

cpp 复制代码
t.request_stop();

实际发生的是:

text 复制代码
request_stop()
    ↓
shared stop-state 标记为
stop requested = true
    ↓
worker 下次检查
st.stop_requested()
    ↓
得到 true
    ↓
worker 自己 cleanup
    ↓
return
    ↓
线程真正结束

所以:

text 复制代码
request_stop()
    ≠
线程已经停止

而只是:

text 复制代码
已经发出"请退出"的请求

15. 协作式取消 Cooperative Cancellation

C++20 采用的核心理念是:

text 复制代码
控制方:
    "请你退出。"

worker:
    "收到,我在安全位置完成清理后退出。"

而不是:

text 复制代码
控制方:
    "现在立刻把线程杀掉。"

这是为了避免线程在任意位置被终止。

例如线程可能正处于:

cpp 复制代码
mutex.lock();

update_shared_state();

// 如果此处被强杀

mutex.unlock();

强杀可能造成:

text 复制代码
mutex 永远不释放
对象析构不执行
文件不关闭
设备状态只更新了一半
数据结构处于不一致状态

因此 C++ 更强调:

text 复制代码
可控
可清理
可预测

的退出过程。


16. std::stop_token 与 std::atomic_bool stop

C++20 之前常见:

cpp 复制代码
std::atomic_bool stop = false;

std::thread t([&] {
    while (!stop.load()) {
        work();
    }
});

stop.store(true);
t.join();

C++20:

cpp 复制代码
std::jthread t([](std::stop_token st) {
    while (!st.stop_requested()) {
        work();
    }
});

t.request_stop();
t.join();

表面逻辑相似:

text 复制代码
atomic_bool

store(true)
    ↓
load()
    ↓
退出

对应:

text 复制代码
stop_token

request_stop()
    ↓
stop_requested()
    ↓
退出

但 stop_token 不只是一个布尔变量。


17. stop_source、stop_token、stop_callback

C++20 把"停止协议"拆成三个角色:

text 复制代码
                 shared stop state
                       │
        ┌──────────────┼──────────────┐
        │              │              │
        ▼              ▼              ▼

  stop_source      stop_token     stop_callback

  请求停止          查询状态        停止时执行动作

17.1 std::stop_source

代表:

有权发出停止请求的一方。

cpp 复制代码
std::stop_source source;

source.request_stop();

17.2 std::stop_token

代表:

只观察停止状态的一方。

cpp 复制代码
std::stop_token token =
    source.get_token();

if (token.stop_requested()) {
    return;
}

stop_token 本身不能:

cpp 复制代码
request_stop();

这实现了权限语义:

text 复制代码
source
    = 控制权

token
    = 观察权

比传:

cpp 复制代码
std::atomic_bool&

更清晰。


17.3 std::stop_callback

可以在停止被请求时执行一个动作。

例如:

cpp 复制代码
void worker(std::stop_token st)
{
    std::stop_callback cb(st, [&] {
        device.cancel_read();
    });

    device.blocking_read();
}

当:

cpp 复制代码
t.request_stop();

发生:

text 复制代码
request_stop()
    ↓
stop-state = requested
    ↓
stop_callback
    ↓
device.cancel_read()
    ↓
blocking_read() 被唤醒
    ↓
worker 可以继续退出

这对于 IO 线程非常重要。


18. request_stop() 可以主动调用,不只在析构时调用

这是本次单独确认的一个重要点。

完全可以:

cpp 复制代码
std::jthread t(worker);

// ...

t.request_stop();

由开发者根据业务时机主动决定。

例如:

text 复制代码
用户点击 Stop
设备断开
应用准备退出
任务被取消
测量流程终止
模式切换

都可以主动:

cpp 复制代码
worker_.request_stop();

18.1 jthread 析构只是最终兜底

可以主动:

cpp 复制代码
t.request_stop();
t.join();

也可以:

cpp 复制代码
t.request_stop();

// 暂时不 join

最终:

text 复制代码
~jthread()
    ↓
request_stop()
    ↓
join()

因此析构行为是:

RAII 最终兜底。

而不是唯一的停止入口。


19. request_stop() 后是否一定要立即 join()

答案:

不一定。

完全可以:

cpp 复制代码
t.request_stop();

do_something_else();

// 不手工 join

最后由:

cpp 复制代码
~jthread()

完成 join()。


19.1 什么时候可以不立即 join()

如果你只希望:

text 复制代码
"你开始退出吧"

那么:

cpp 复制代码
t.request_stop();

即可。

调用线程可以继续做其它事情:

cpp 复制代码
t.request_stop();

save_ui_state();
write_log();
cleanup_other_resources();

这样 worker 与调用线程甚至可以并行清理。


19.2 什么时候必须 join()

如果后续逻辑要求:

从这一行开始,我必须保证 worker 已彻底结束。

那么必须:

cpp 复制代码
t.request_stop();
t.join();

例如:

cpp 复制代码
worker_.request_stop();
worker_.join();

device_.close();

因为:

cpp 复制代码
request_stop();

返回时 worker 可能仍然正在:

cpp 复制代码
device_.read();

20. request_stop() 与 join() 的职责完全不同

建议直接记住:

text 复制代码
request_stop()
    =
"请开始退出"


join()
    =
"我在这里等到你真正退出"

完整生命周期:

text 复制代码
request_stop()
    ↓
停止请求发出
    ↓
worker 检查 stop_token
    ↓
退出主循环
    ↓
cleanup
    ↓
return
    ↓
执行线程真正结束
    ↓
join()
    ↓
调用方确认退出完成

所以:

cpp 复制代码
t.request_stop();
t.join();

不是重复操作。

它们分别对应:

text 复制代码
控制请求
+
完成确认

21. request_stop() 不保证阻塞操作立即结束

这是使用 stop_token 时最容易犯的错误之一。

例如:

cpp 复制代码
void worker(std::stop_token st)
{
    while (!st.stop_requested()) {

        blocking_read();
    }
}

如果 worker 卡在:

cpp 复制代码
blocking_read();

即使另外一个线程:

cpp 复制代码
t.request_stop();

也只是:

text 复制代码
stop-state = requested

worker 并没有机会执行:

cpp 复制代码
st.stop_requested();

因此仍可能一直阻塞。


22. sleep_for() 也不会自动响应 stop_token

例如:

cpp 复制代码
void worker(std::stop_token st)
{
    while (!st.stop_requested()) {

        std::this_thread::sleep_for(
            std::chrono::seconds(30)
        );
    }
}

如果刚进入 30 秒 sleep 就:

cpp 复制代码
t.request_stop();

线程不会自动立即醒来。

可能:

text 复制代码
0s      sleep_for(30s)
1s      request_stop()
        ↓
        仍在 sleep
30s     sleep 结束
        ↓
        检查 stop_requested()
        ↓
        return

所以:

stop_token 是停止状态通知,不是系统级线程 interrupt。


23. 一个可靠的线程退出设计:三阶段

对于设备控制、采集线程、后台服务,推荐把退出设计成三个阶段:

text 复制代码
① request stop
       ↓
② break blocking operation
       ↓
③ cleanup + return

例如:

cpp 复制代码
void DeviceWorker::run(std::stop_token st)
{
    std::stop_callback cancel(st, [this] {
        port_.cancel();
    });

    while (!st.stop_requested()) {

        auto command = wait_command();

        if (st.stop_requested()) {
            break;
        }

        execute(command);
    }

    stop_motion_if_needed();
    flush_pending_commands();
    close_device();
}

外部:

cpp 复制代码
worker_.request_stop();
worker_.join();

对应语义:

text 复制代码
request_stop()
    ↓
不要再接受新工作,准备退出

port_.cancel()
    ↓
把阻塞中的 worker 唤醒

cleanup
    ↓
让设备恢复安全状态

return
    ↓
线程真正结束

join()
    ↓
调用方确认退出完成

24. jthread 的析构顺序与成员生命周期

假设:

cpp 复制代码
class DeviceController
{
    Device device_;
    std::jthread worker_;
};

C++ 成员按声明顺序的逆序析构:

text 复制代码
worker_ 先析构
    ↓
request_stop + join
    ↓
device_ 后析构

这是比较安全的。

但如果写成:

cpp 复制代码
class DeviceController
{
    std::jthread worker_;
    Device device_;
};

析构顺序:

text 复制代码
device_ 先析构
    ↓
worker_ 后析构

如果 worker 仍访问:

cpp 复制代码
device_

就可能出现生命周期问题。

工程上如果线程依赖多个成员资源,建议显式控制退出:

cpp 复制代码
DeviceController::~DeviceController()
{
    worker_.request_stop();

    if (worker_.joinable()) {
        worker_.join();
    }

    // 从这里开始可以确信:
    // worker 不再访问任何成员
}

25. std::thread、std::jthread、std::async 的生命周期对比

工具 核心关注点 析构时仍有任务 停止机制
std::thread 线程 若仍 joinable() → std::terminate() 自己设计
std::jthread RAII 线程 request_stop() + join() stop_token
std::async 异步任务 + 结果 async 关联 future 最终析构可能等待 无标准主动取消
std::future 异步结果 管理 shared state 不负责取消任务

26. join()、request_stop()、get() 不要混为一谈

request_stop()

text 复制代码
作用:
    发出"请退出"的请求

不保证:
    线程已经退出

join()

text 复制代码
作用:
    等待线程真正结束
    完成线程关联状态收尾
    建立同步关系

不负责:
    请求线程退出

future::get()

text 复制代码
作用:
    等待异步结果 ready
    取得返回值
    重新抛出任务异常

不负责:
    强制停止任务

27. 一个非常重要的统一心智模型

27.1 对 jthread

text 复制代码
控制线程
    │
request_stop()
    │
    ▼
shared stop-state
    │
    ├──────────────► stop_token
    │                   │
    │             stop_requested()
    │                   │
    │                   ▼
    │                worker
    │                   │
    │                cleanup
    │                   │
    │                 return
    │                   │
    ▼                   ▼
join() <────────── worker 已结束
    │
    ▼
继续执行

27.2 对 async

text 复制代码
std::async()
    │
    ├──────────────► async task
    │                    │
    │                 return
    │                    │
    │                    ▼
    │              shared state
    │                    │
    ▼                    ▼
std::future <──────── result / exception
    │
    ▼
get()

28. 在 wxWidgets + Boost.Asio 工程中的建议

对于桌面工业软件,可以继续维持:

text 复制代码
wxWidgets UI thread
        │
        │ asio::post
        ▼
Boost.Asio worker / strand
        │
        │ device IO
        ▼
得到结果
        │
        │ CallAfter / wx event
        ▼
wxWidgets UI thread

不要因为学了 std::async 就把 UI 改成:

cpp 复制代码
auto f = std::async(
    std::launch::async,
    read_device
);

auto result = f.get(); // UI thread blocking

因为:

text 复制代码
async 解决"任务在哪里执行"

但:

text 复制代码
future.get()
仍然会阻塞调用线程

因此 UI 线程仍然推荐:

text 复制代码
callback
event
CallAfter
wxQueueEvent
asio completion handler

29. std::async 适合什么场景

比较适合:

text 复制代码
一次性 CPU 计算
少量粗粒度并行任务
后台计算并最终得到 Result
测试代码等待一次异步结果
临时并行化同步函数
fork → compute → join 模型

例如:

cpp 复制代码
auto fa = std::async(
    std::launch::async,
    compute_a
);

auto fb = std::async(
    std::launch::async,
    compute_b
);

auto a = fa.get();
auto b = fb.get();

merge(a, b);

30. std::async 不适合什么场景

通常不适合:

text 复制代码
大量小任务
线程池
长期设备 IO loop
串口持续接收
相机 acquisition loop
持续事件流
高频状态刷新
复杂任务取消
精细线程 affinity / priority 控制

这些更适合:

text 复制代码
Boost.Asio
thread pool
executor
callback
message queue
event queue
observer
专用 worker thread

31. 工程实践建议

31.1 使用 std::async 时

如果希望并行:

cpp 复制代码
auto f = std::async(
    std::launch::async,
    work
);

不要随手:

cpp 复制代码
std::async(
    std::launch::async,
    work
);

否则临时 future 可能让当前语句结束时直接等待。


31.2 使用 std::thread 时

必须明确:

cpp 复制代码
t.join();

或:

cpp 复制代码
t.detach();

不要让一个:

cpp 复制代码
joinable() == true

的 std::thread 直接析构。


31.3 C++20 优先考虑 std::jthread

如果任务天然具有:

text 复制代码
启动
运行
请求停止
清理
退出

生命周期,可以优先:

cpp 复制代码
std::jthread
+
std::stop_token

而不是自己维护:

cpp 复制代码
std::thread
+
std::atomic_bool stop_
+
手写析构 join

31.4 不要把 request_stop() 当作线程已经结束

错误:

cpp 复制代码
worker_.request_stop();

device_.destroy(); // 认为 worker 已不再访问 device

正确:

cpp 复制代码
worker_.request_stop();
worker_.join();

device_.destroy();

如果资源销毁要求 worker 已彻底结束,就必须等待。


32. 最终记忆版

std::async

text 复制代码
我要执行一个任务并得到结果

std::future

text 复制代码
我要观察 / 等待 / 取得异步结果

std::thread

text 复制代码
我要直接管理一个执行线程

std::jthread

text 复制代码
我要一个具备 RAII join
并支持协作式停止的线程

request_stop()

text 复制代码
"请开始退出"

stop_token

text 复制代码
"有人请求我退出了吗?"

stop_callback

text 复制代码
"收到退出请求时,顺便执行取消 IO / 唤醒等待等动作"

join()

text 复制代码
"我在这里等到线程真正结束,
并完成线程生命周期收尾"

future::get()

text 复制代码
"我在这里等结果,
取回结果或重新抛出异步任务异常"

33. 最关键的四个结论

结论 1

text 复制代码
request_stop()
≠
线程已经停止

它只是提出停止请求。


结论 2

text 复制代码
join()
≠
让线程退出

它负责等待并确认线程已经结束。


结论 3

text 复制代码
std::thread 析构时仍 joinable
    ↓
std::terminate()
    ↓
整个进程终止

不是只结束那个 worker thread。


结论 4

text 复制代码
std::async(std::launch::async, work);

如果不保存返回的 future:

text 复制代码
临时 future
    ↓
当前语句结束时析构
    ↓
必要时等待 async task
    ↓
调用线程无法继续执行后续代码

因此可能失去原本想要的并行效果。


34. 一句话总结整套体系

std::thread 管线程,std::async 管任务结果,std::jthread 管具备 RAII 与协作式退出的线程;request_stop() 发出退出意图,worker 自己安全退出,而 join() 负责等待并确认退出已经完成。

相关推荐
蜗牛互联网2 小时前
Python + JSON Schema 实现工单结构化输出与本地复核
java·人工智能·后端
逐光者9332 小时前
STM32——SPI 屏 · Flash · 高速
java·前端·stm32
wuminyu2 小时前
ForkJoinPool窃取线程在扫描其他WorkQueue时的随机采样算法与Phase线程挂起唤醒机制解析
java·linux·c语言·jvm·c++
longlongzihan2 小时前
链表找环:Floyd 判圈算法(LeetCode 141 & 142 )
c++·算法·leetcode·链表
Sarvartha2 小时前
内部类知识
java·开发语言
Knight_AL2 小时前
从 EasyExcel 到 Apache Fesod:Java Excel 处理生态的一次重构
java·apache·excel
yuniko-n2 小时前
【JUC】集合线程安全
java
liulilittle2 小时前
Linux 下 select 测试函数
linux·服务器·网络·数据库·c++·select·c
DongQiShanRen2 小时前
玄龙(上):TICK 主循环——意识心跳怎么跳
linux·jvm·数据库·人工智能·数据挖掘·rust