39、C++20 核心特性:std::jthread,自动join的线程,更安全。
多线程编程,一直是C++里让人又爱又恨的部分。爱的是它能榨干CPU每一滴性能,恨的是稍不留神就给你来个野指针、死锁或者线程泄漏。我个人习惯用std::thread的时候,每次都要小心翼翼地记着join或者detach,一旦忘了,程序直接崩溃给你看。
C++20 带来的 std::jthread,说白了就是帮你自动收尾的线程。你只管启动它,剩下的销毁、等待、清理,它自己搞定。嗯,这感觉就像请了个靠谱的管家,不用你操心关门关窗了。
从 std::thread 到 std::jthread
先回忆一下 std::thread 的痛点。你写了个线程,如果忘记 join 或 detach,析构时会调用 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_source 和 std::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 是 C++20 里一个非常实用的改进。它解决了 std::thread 长期以来的痛点——忘记 join 导致的程序崩溃。同时,内置的停止令牌让线程协作变得更加优雅和标准化。
我个人建议,在新项目中尽量使用 std::jthread 替代 std::thread。除非你有非常特殊的需求(比如必须 detach 线程),否则 std::jthread 总是更好的选择。它让代码更安全,也让你的睡眠质量更高——至少不用担心线上服务因为线程泄漏而挂掉了。
一句话总结: std::jthread = std::thread + 自动 join + 停止令牌。更安全,更现代,值得你立刻用起来。
公众号:蓝海资料掘金营,微信deep3321