39、C++20 核心特性:std::jthread,自动join的线程,更安全。

多线程编程,一直是C++里让人又爱又恨的部分。爱的是它能榨干CPU每一滴性能,恨的是稍不留神就给你来个野指针、死锁或者线程泄漏。我个人习惯用std::thread的时候,每次都要小心翼翼地记着join或者detach,一旦忘了,程序直接崩溃给你看。

C++20 带来的 std::jthread,说白了就是帮你自动收尾的线程。你只管启动它,剩下的销毁、等待、清理,它自己搞定。嗯,这感觉就像请了个靠谱的管家,不用你操心关门关窗了。

从 std::thread 到 std::jthread

先回忆一下 std::thread 的痛点。你写了个线程,如果忘记 joindetach,析构时会调用 std::terminate(),程序直接挂掉。我在项目中遇到过好几次这种问题,尤其是异常路径下,join 没被执行,线上服务就莫名其妙崩溃了。

std::jthread 的 "j" 代表 "joining"。它的析构函数会自动调用 join(),确保线程在对象销毁前完成执行。你想想看,这省掉了多少心智负担。

核心区别一句话: std::thread 需要你手动管理生命周期,std::jthread 自动帮你 join。

基本用法:自动 join 的魔力

来看个最简单的例子:

#include <thread>
#include <iostream>

void worker() {
    std::cout << "线程工作中..." << std::endl;
}

int main() {
    std::jthread t(worker);
    // 不需要调用 t.join()
    // 离开作用域时,析构函数自动 join
    return 0;
}

你看,代码干净多了。以前用 std::thread,我总要在函数末尾补一句 t.join(),少写了就是灾难。现在 std::jthread 帮你兜底,心里踏实多了。

请求停止:优雅的线程协作

std::jthread 还有一个杀手锏——内置的停止令牌(std::stop_token)。它让线程间的协作停止变得非常优雅。

为什么会需要这个?以前我们想停止一个线程,通常得自己定义一个 std::atomic<bool> 标志位,然后线程里轮询检查。代码写起来又臭又长,还容易出错。

std::jthread 直接给你配好了 std::stop_sourcestd::stop_token。你只需要在线程函数里接受一个 std::stop_token 参数,然后调用 request_stop() 就能发出停止信号。

#include <thread>
#include <iostream>
#include <chrono>

void worker(std::stop_token st) {
    while (!st.stop_requested()) {
        std::cout << "工作中..." << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    std::cout << "收到停止信号,退出" << std::endl;
}

int main() {
    std::jthread t(worker);
    std::this_thread::sleep_for(std::chrono::seconds(2));
    t.request_stop();  // 优雅地请求停止
    // 自动 join
    return 0;
}

我曾经在项目里用 std::atomic<bool> 实现过类似功能,代码量至少多了一倍,而且还要处理各种边界情况。现在 std::jthread 把这些都封装好了,直接拿来用就行。

停止令牌的协作机制

停止令牌不是强制的,它更像是一种协作协议。线程可以选择响应停止请求,也可以忽略。这给了开发者很大的灵活性。

组件 作用
std::stop_source 停止信号的源头,可以调用 request_stop() 发出请求
std::stop_token 线程内部查询停止状态的令牌
std::stop_callback 注册一个回调,当停止请求发出时自动执行

你想想看,这种设计让线程的停止变得非常可控。不是粗暴地 terminate,而是给线程一个机会去清理资源、保存状态,然后再安全退出。

小技巧: 如果你的线程里有阻塞操作(比如等待条件变量),可以用 st.stop_requested() 配合 wait_for 来实现超时停止,非常灵活。

与 std::thread 的对比

我整理了一张对比表,方便你快速了解两者的差异:

特性 std::thread std::jthread
析构行为 未 join/detach 则 terminate 自动 join
停止机制 无内置支持 内置 stop_token/stop_source
可移动性 支持移动 支持移动
可复制性 不可复制 不可复制
使用复杂度 较高,需手动管理 较低,自动管理

说白了,std::jthread 就是 std::thread 的升级版。它保留了所有核心能力,同时解决了最让人头疼的生命周期问题。

避坑指南:我曾经踩过的坑

虽然 std::jthread 很安全,但也不是万能的。我曾经遇到过几个问题,分享给你:

  • 移动语义要小心: std::jthread 只能移动,不能复制。如果你不小心复制了,编译器会报错。我一开始没注意,把 std::jthread 对象传给了函数,结果编译失败。
  • 析构顺序: 如果多个 std::jthread 对象同时存在,它们的析构顺序是反序的。这可能导致某些线程在依赖的资源被释放后才开始 join,引发问题。我建议你显式管理好线程的生命周期,不要完全依赖自动 join。
  • 停止令牌不是万能的: 如果线程卡在某个无限循环或者阻塞 I/O 里,request_stop() 不会强制中断它。你需要在线程内部定期检查 stop_requested()

警告: 不要在一个已经被移动过的 std::jthread 对象上调用 join() 或 request_stop()。移动后,原对象不再关联任何线程,调用这些方法会导致未定义行为。

知识体系图

下面这张图展示了 std::jthread 的核心知识结构,帮你快速理清脉络:

std::jthread 自动 join:析构时自动等待线程结束 停止令牌:内置 stop_token / stop_source 子组件:std::stop_source / std::stop_token / std::stop_callback 对比 std::thread:更安全、更易用、更现代 注意:移动语义、析构顺序、停止令牌局限性

总结

std::jthread 是 C++20 里一个非常实用的改进。它解决了 std::thread 长期以来的痛点——忘记 join 导致的程序崩溃。同时,内置的停止令牌让线程协作变得更加优雅和标准化。

我个人建议,在新项目中尽量使用 std::jthread 替代 std::thread。除非你有非常特殊的需求(比如必须 detach 线程),否则 std::jthread 总是更好的选择。它让代码更安全,也让你的睡眠质量更高——至少不用担心线上服务因为线程泄漏而挂掉了。

一句话总结: std::jthread = std::thread + 自动 join + 停止令牌。更安全,更现代,值得你立刻用起来。


公众号:蓝海资料掘金营,微信deep3321