Wiki
jthread 与 stop_token
An English translation is not available yet. The latest Simplified Chinese source is shown without triggering paid translation.
C++20 提供了:
std::jthread
它可以理解成一个更适合现代 C++ 的线程类。
和 std::thread 相比,它最重要的改进有两个:
1. 生命周期更安全
→ 析构时自动处理线程,不容易因为漏写 join() 而出问题
2. 自带协作式停止机制
→ 可以通过 stop_token / request_stop() 请求线程安全退出
所以这一节最重要的是把两个概念分开:
线程生命周期管理
→ 谁负责等待线程结束
线程停止协议
→ 谁发出停止请求,工作线程怎么响应
std::jthread 同时解决了这两个问题,但它并不是“自动强杀线程”的工具。
1.为什么需要 std::jthread
1.1.std::thread 的问题
使用普通 std::thread:
std::thread thread(work);
在线程对象析构之前,必须保证它已经:
thread.join();
或者:
thread.detach();
否则,如果一个仍然 joinable() 的 std::thread 被析构,程序会调用:
std::terminate();
例如:
#include <print>
#include <thread>
void work()
{
std::println("working");
}
int main()
{
std::thread thread(work);
// 如果这里忘记 join() / detach()
}
程序离开 main() 时,thread 仍然是 joinable 的,于是会触发 std::terminate()。

真正麻烦的是,当程序中出现:
异常
提前 return
多个 if / else 分支
复杂资源管理
就更容易漏掉 join()。
这和手动管理 new/delete 有点像:
代码简单时看起来没问题
控制路径一复杂
→ 很容易忘记收尾
而现代 C++ 更倾向于通过 RAII 自动管理资源。
线程也一样。
1.2.std::jthread 会自动处理线程结束
最简单的例子:
#include <print>
#include <thread>
void work()
{
std::println("working");
}
int main()
{
std::jthread thread(work);
}
运行结果:
working

这里没有写:
thread.join();
但程序仍然可以正常结束。
因为当:
std::jthread thread;
离开作用域时,如果它仍然是 joinable 的,析构函数会负责收尾。
可以先粗略理解成:
std::thread
→ 需要自己保证 join() / detach()
std::jthread
→ 析构时自动处理
不过 jthread 的析构不只是简单地 join()。
更准确地说,如果它仍然 joinable,析构过程概念上会做:
request_stop()
↓
join()
也就是:
先请求线程停止,然后等待线程真正结束。
这也是 jthread 和普通 thread 一个很重要的区别。
1.3.jthread 不会强制杀死线程
std::jthread 使用的是:
协作式停止(cooperative cancellation)
所谓“协作式”,意思是:
某个线程:
发出停止请求
↓
工作线程:
看到停止请求
↓
工作线程:
自己选择一个安全的位置退出
而不是:
request_stop()
↓
操作系统立刻把工作线程强行杀掉
标准库不会随便把线程从某条机器指令中间直接掐死。
原因很简单。
线程可能正在:
持有 mutex
修改共享数据
写文件
操作硬件
分配 / 释放资源
修改对象内部状态
如果在任意位置强制终止线程,很容易把程序状态搞坏。
所以:
thread.request_stop();
更像是在告诉工作线程:
“你该停了,请在合适的位置自己退出。”
而不是:
“现在立刻把你杀掉。”
2.stop_token 停止协议
2.1.std::stop_token
2.1.1.stop_token 是什么
std::stop_token 可以理解成:
停止请求的观察端。
它本身不能发出停止请求,只能查看:
有没有人要求我停止?
例如:
stop_token.stop_requested();
返回:
true → 已经有人请求停止
false → 目前还没有停止请求
jthread 内部有一个停止状态,可以粗略理解成:
jthread / stop_source
↓
发出停止请求
共享停止状态
↑
查看停止请求
↑
stop_token
也就是说:
request_stop()
→ 写入“停止请求”
stop_requested()
→ 查看“停止请求”
2.2.jthread 可以自动传入 stop_token
来看一个最典型的例子:
#include <chrono>
#include <print>
#include <stop_token>
#include <thread>
using namespace std::chrono_literals;
void worker(std::stop_token token)
{
while (!token.stop_requested())
{
std::println("working");
std::this_thread::sleep_for(100ms);
}
std::println("stopping");
}
int main()
{
std::jthread thread(worker);
std::this_thread::sleep_for(350ms);
thread.request_stop();
}
可能输出:
working
working
working
working
stopping
这里最特别的是:
void worker(std::stop_token token)
但创建线程时却只写了:
std::jthread thread(worker);
我们并没有手动传:
worker(token);
因为 std::jthread 发现这个函数可以接收 std::stop_token,于是会自动把自己的 stop token 传进去。
可以近似理解成:
std::jthread thread(worker);
内部效果类似:
worker(jthread 自动提供的 stop_token);
这里不可以显式填参数,比如std::jthread thread(worker, thread.get_stop_token());,
这是非法的,原因非常直接:在执行构造函数参数时,thread 对象还没有构造完成,所以你还不能调用thread.get_stop_token()。
所以这里直接记住他会自动把自己的stop token传入进去就行了,不需要我们手动传入。
2.3.request_stop()
调用:
thread.request_stop();
表示:
向这个
jthread关联的停止状态发出停止请求。
它只负责“发请求”。
它不会:
强制终止线程
自动跳出 while
直接让 worker return
等待线程结束
真正决定什么时候退出的是工作线程。
例如:
while (!token.stop_requested())
{
do_work();
}
每轮循环检查:
token.stop_requested()
一旦发现停止请求,就退出循环。
整个流程是:
main
│
│ 创建 jthread
↓
worker 开始执行
│
│ while (!token.stop_requested())
│
│ working...
│
│ working...
│
↑
main 调用 request_stop()
│
↓
停止状态变成“已请求停止”
│
↓
worker 下一次检查 stop_requested()
│
↓
发现为 true
│
↓
退出循环
所以最重要的一句话是:
request_stop()只是请求停止,真正停止必须由工作线程自己配合。
2.4.request_stop() 的返回值
request_stop() 会返回一个 bool:
bool result = thread.request_stop();
可以粗略理解成:
true
→ 这次调用真的把状态从
“还没请求停止”
改成了
“已经请求停止”
false
→ 之前已经请求过停止
或者当前停止状态无法产生停止请求
普通业务代码经常不需要关心这个返回值。
直接写:
thread.request_stop();
就可以。
2.5.stop_possible()
stop_token 还可以调用:
token.stop_possible();
它表示:
这个 token 是否关联着一个“有可能收到停止请求”的停止状态。
例如:
std::stop_token token;
这是一个默认构造的 token,它没有关联正常的停止状态。
此时通常:
token.stop_possible() == false
token.stop_requested() == false
而 std::jthread 自动提供的 token 通常是:
stop_possible() == true
对于普通使用来说,最常用的还是:
stop_requested()
stop_possible() 更多是在需要判断“这个 token 到底支不支持停止协议”时使用。
2.6.jthread 析构时到底发生什么
假设:
{
std::jthread thread(worker);
}
执行到右花括号:
}
thread 开始析构。
如果它仍然是 joinable 的,那么概念上会:
thread.request_stop();
↓
thread.join();
所以:
{
std::jthread thread(worker);
}
可以理解成退出作用域时自动做了:
thread.request_stop();
thread.join();
这也是为什么这种代码可以正常工作:
#include <print>
#include <stop_token>
#include <thread>
void worker(std::stop_token token)
{
while (!token.stop_requested())
{
// work
}
std::println("stopped");
}
int main()
{
std::jthread thread(worker);
}
main() 结束时:
jthread 析构
↓
request_stop()
↓
worker 看到停止请求
↓
worker 退出
↓
join() 等待结束
↓
析构完成
2.7.但析构也不能“凭空让线程退出”
下面这个线程完全不检查 stop_token:
void worker(std::stop_token)
{
for (;;) //死循环,效率比while(true)高
{
}
}
然后:
int main()
{
std::jthread thread(worker);
}
离开 main() 时:
jthread 析构
↓
request_stop()
↓
停止请求确实发出了
↓
但是 worker 根本不检查
↓
for(;;) 继续运行
↓
析构函数接着 join()
↓
join() 一直等
程序就会一直卡在那里。
因此:
jthread提供的是停止协议,不是强制终止能力。
所以对于长期运行的 worker,一般应该在合适的位置检查:
token.stop_requested()
例如:
while (!token.stop_requested())
{
do_some_work();
}
而不是只在线程开头检查一次:
if (token.stop_requested())
{
return;
}
// 后面运行几个小时都不再检查
2.8.阻塞操作也会影响停止速度
即使代码写成:
while (!token.stop_requested())
{
blocking_operation();
}
也不代表线程一定能马上停止。
假设:
blocking_operation();
一次会阻塞 30 秒。
那么即使外部已经执行:
thread.request_stop();
工作线程也必须先从:
blocking_operation();
返回,才能进行下一轮:
token.stop_requested()
所以停止响应速度还取决于工作线程内部在干什么。
对于需要快速停止的线程,要考虑:
阻塞函数有没有超时
阻塞操作能不能被唤醒
能不能把长任务拆成多个小步骤
能不能在多个安全点检查 stop_requested()
例如:
while (!token.stop_requested())
{
do_small_step();
}
通常比:
while (!token.stop_requested())
{
do_one_huge_blocking_job();
}
更容易快速响应停止请求。
2.9.std::stop_source
前面说过:
stop_token
→ 观察停止请求
那么谁负责发出停止请求?
除了 jthread.request_stop(),标准库还提供:
std::stop_source
它可以理解成:
停止请求的控制端。
例如:
#include <print>
#include <stop_token>
int main()
{
std::stop_source source;
std::stop_token token = source.get_token();
std::println("{}", token.stop_requested());
source.request_stop();
std::println("{}", token.stop_requested());
}
运行结果:
false
true
整个关系可以理解成:
stop_source
│
│ request_stop()
↓
共享停止状态
↑
│ stop_requested()
stop_token
所以:
std::stop_source source;
负责:
发请求
而:
std::stop_token token = source.get_token();
负责:
看请求
2.10.多个 stop_token 可以观察同一个停止状态
例如:
std::stop_source source;
std::stop_token token1 = source.get_token();
std::stop_token token2 = source.get_token();
std::stop_token token3 = source.get_token();
这三个 token 都关联到同一个停止状态。
当:
source.request_stop();
之后:
token1.stop_requested()
token2.stop_requested()
token3.stop_requested()
都会变成:
true
所以 stop_source + stop_token 很适合表达:
一个管理者
↓
统一发出停止请求
↓
多个任务 / 对象
↓
一起观察同一个停止状态
2.11.std::stop_callback
有时候我们不想一直手动检查:
token.stop_requested()
而是希望:
一旦停止请求出现,就自动执行某个操作。
可以使用:
std::stop_callback
例如:
#include <print>
#include <stop_token>
int main()
{
std::stop_source source;
std::stop_token token = source.get_token();
std::stop_callback callback(token, [] {
std::println("stop requested");
});
source.request_stop();
}
运行结果:
stop requested
也就是说:
request_stop()
↓
停止状态改变
↓
已经注册的 stop_callback 被调用
他如果是jthread调用的话,那么顺序是:
request_stop()
↓
同步执行 stop_callback
↓
request_stop() 返回
↓
join()
也就是说,callback 先于 join() 完成。std::jthread 的析构本身就是先调用 request_stop(),再调用 join()。
它很适合干一些很轻量的事情,例如:
设置状态
唤醒某个等待对象
通知某个组件开始收尾
但不建议在 callback 里面做:
复杂计算
长时间 IO
长时间阻塞
大量锁操作
因为停止路径本身应该尽量简单、可控。
还有一个细节:
如果注册 callback 时,停止请求已经发生,那么 callback 可能会在注册过程中直接执行。
所以不要假设:
callback 一定会在未来某个时间
由另一个线程执行
3.jthread 的线程函数和生命周期操作
3.1.jthread 不一定非要使用 stop_token
std::jthread 也可以像普通 std::thread 一样运行普通函数。
例如:
#include <print>
#include <thread>
void work(int value)
{
std::println("{}", value);
}
int main()
{
std::jthread thread(work, 42);
}
运行结果:
42
这里:
void work(int value)
根本没有 stop_token。
没有关系。
jthread 会判断这个函数能不能接收自动提供的 stop token。
可以简单理解成:
如果函数可以这样调用:
worker(stop_token, args...)
→ 自动传入 stop_token
否则如果函数只能这样调用:
worker(args...)
→ 就像普通 thread 一样调用
所以两种写法都可以:
void worker(std::stop_token token);
以及:
void worker();
3.2.带额外参数的 stop_token 线程函数
例如:
#include <print>
#include <stop_token>
#include <thread>
void worker(std::stop_token token, int id)
{
std::println("worker {}", id);
while (!token.stop_requested())
{
// work
}
}
int main()
{
std::jthread thread(worker, 7);
thread.request_stop();
}
这里:
std::jthread thread(worker, 7);
可以理解成最终调用:
worker(自动提供的 stop_token, 7);
所以如果函数需要 stop token,参数顺序通常是:
void worker(std::stop_token token, 参数1, 参数2, ...)
例如:
void worker(
std::stop_token token,
int id,
std::string name);
创建:
std::jthread thread(worker, 7, "camera");
相当于:
worker(
自动提供的 stop_token,
7,
"camera"
)
3.3.jthread 仍然可以手动 join()
虽然 jthread 会自动 join,但你仍然可以自己写:
std::jthread thread(work);
thread.join();
执行完:
thread.join();
以后:
thread.joinable()
会变成:
false
因为这个 jthread 已经不再关联一个可 join 的执行线程。
所以:
自动 join
并不代表:
不能手动 join
它只是意味着:
如果你没手动处理,析构函数会帮你安全收尾。
3.4.jthread 也可以 detach(),但通常不推荐
jthread 仍然提供:
thread.detach();
但这样做以后:
jthread 不再拥有这个线程
析构时也没办法再 join 它
于是 jthread 最重要的 RAII 生命周期优势基本就没有了。
所以如果你发现自己准备写:
thread.detach();
最好先问几个问题:
这个线程以后由谁负责?
程序退出时它怎么结束?
它访问的对象会不会已经析构?
它访问的资源能活多久?
除非你非常明确地知道线程生命周期怎么管理,否则一般不建议随便 detach。
3.5.condition_variable_any 与 stop_token
前面提到一个问题:
如果线程正在:
condition_variable.wait(...)
里面睡眠,那么只检查:
token.stop_requested()
是不够的。
因为线程可能根本没机会执行到下一次检查。
C++20 的:
std::condition_variable_any
提供了可以直接配合 stop_token 的等待方式。
例如:
#include <condition_variable>
#include <mutex>
#include <stop_token>
#include <thread>
std::mutex mutex;
std::condition_variable_any cv;
bool ready = false;
void worker(std::stop_token token)
{
std::unique_lock lock(mutex);
bool condition_met = cv.wait(
lock,
token,
[] {
return ready;
});
if (!condition_met)
{
// 没等到 ready == true,
// 而是因为停止请求结束等待
return;
}
// ready == true
}
可以把这次等待理解成:
等待两个条件中的任意一个:
1. ready == true
或者
2. 收到停止请求
如果:
condition_met == true
表示谓词:
ready == true
成立。
如果:
condition_met == false
说明这次等待并不是因为 ready 成立而结束,通常就是因为收到了停止请求。
这样工作线程即使正在条件变量里等待,也能够响应 jthread 的停止协议。
4.选择与常见错误
4.1.std::thread 和 std::jthread 对比
| 特性 | std::thread |
std::jthread |
|---|---|---|
| 引入版本 | C++11 | C++20 |
| 创建线程 | ✅ | ✅ |
手动 join() |
✅ | ✅ |
手动 detach() |
✅ | ✅ |
| 析构时自动收尾 | ❌ | ✅ |
joinable 状态析构会 terminate() |
✅ | ❌ |
| 自带停止请求机制 | ❌ | ✅ |
自动提供 stop_token |
❌ | ✅ |
| 适合长期可停止 worker | 一般 | ✅ |
如果项目可以使用 C++20,对于这种线程:
启动
↓
持续运行
↓
程序退出时请求停止
↓
安全结束
通常优先考虑:
std::jthread
4.2.atomic<bool> running 和 stop_token 怎么选
传统代码经常这样写:
std::atomic<bool> running{true};
void worker()
{
while (running.load())
{
// work
}
}
停止时:
running.store(false);
这种写法没有问题。
对于非常简单的线程,它甚至很直观。
例如:
running == true
→ 继续运行
running == false
→ 停止
但是:
std::stop_token
提供的是一套标准化的停止协议。
它可以和:
std::jthread
std::stop_source
std::stop_callback
std::condition_variable_any
直接配合。
可以理解成:
atomic<bool>
→ 只是一个线程安全的 bool
→ “true / false 到底是什么意思”由你自己规定
而:
stop_token
→ 专门表达“停止请求”
→ 标准库里的多个工具都认识这种协议
因此:
非常简单的开关
→ atomic<bool> 完全可以
明确的线程停止生命周期
→ jthread + stop_token 更合适
4.3.常见错误
4.3.1.错误:以为 request_stop() 会强制杀死线程
错误理解:
request_stop()
→ 线程立刻消失
正确理解:
request_stop()
→ 发出停止请求
→ 工作线程自己检查并退出
4.3.2.错误:worker 从来不检查 stop_requested()
例如:
void worker(std::stop_token)
{
while (true)
{
do_work();
}
}
即使 jthread 析构时自动:
request_stop();
这个线程也不会退出。
最终析构中的:
join();
会一直等。
4.3.3.错误:检查了停止请求,但中间阻塞太久
例如:
while (!token.stop_requested())
{
blocking_operation(); // 一卡几十秒
}
停止请求虽然已经发出,但线程要等到阻塞操作返回后才能再次检查。
4.3.4.错误:用了 jthread 又随手 detach()
这样会失去:
自动 join
自动停止请求
RAII 生命周期管理
这些主要优势。
4.3.5.错误:在 stop_callback 里做大量复杂工作
callback 更适合:
轻量通知
设置状态
唤醒等待者
而不是承担复杂业务逻辑。
4.3.6.错误:以为“收到停止请求”就会立刻终止线程(重要)
request_stop() 只是发出停止请求,并不会强制中断工作线程。
工作线程通常会在某些安全检查点主动检查:
token.stop_requested()
例如:
while (!token.stop_requested())
{
finish_one_small_task();
}
如果停止请求恰好在 finish_one_small_task() 执行过程中到来,这个函数不会被强行执行到一半就终止。
线程通常会先完成当前这一小块工作,然后回到循环条件:
token.stop_requested()
发现停止请求后,再退出循环并结束线程。
可以理解成:
收到停止请求
↓
完成当前不能被随意打断的小任务
↓
到达安全检查点
↓
发现 stop_requested() == true
↓
退出循环
↓
完成必要的收尾
↓
线程结束
而 join() 的作用只是:
等待这个线程真正结束。
所以典型关系是:
request_stop()
→ 请求线程停止
worker 在安全位置响应请求并退出
join()
→ 等待 worker 退出完成
所谓“合适、安全的位置”,就是线程状态已经完整、资源处于一致状态,此时退出不会留下半完成操作的位置。
安全点 = 可以开始退出的位置;函数 return = 线程真正退出。
所以可以这么理解例子中的worker函数 返回 ≈ 这个线程执行结束。
5.小结
std::jthread 可以理解成:
带 RAII 生命周期管理和标准停止协议的
std::thread。
最重要的几个点:
std::thread
→ 必须自己保证 join() / detach()
std::jthread
→ 析构时自动 request_stop() + join()
停止机制:
request_stop()
→ 发出停止请求
stop_token.stop_requested()
→ 查看是否收到停止请求
它是:
协作式停止
而不是:
强制杀死线程
另外:
stop_source
→ 发出停止请求
stop_token
→ 观察停止请求
stop_callback
→ 停止请求出现时执行回调
对于 C++20 中“启动后持续运行、退出时需要安全停止”的后台线程,一般可以优先考虑:
std::jthread + std::stop_token
而不是:
std::thread + 手写生命周期管理 + 自定义 atomic<bool> 停止协议