std::stop_token是c++20引入的协作式线程取消机制核心组件,用于让线程主动检查停止请求并安全清理退出,解决传统bool标志竞态、阻塞调用无法及时响应及手动管理生命周期易出错等问题。

std::stop_token 是什么,它能解决什么问题
std::stop_token 是 C++20 引入的协作式线程取消机制核心组件,不是“强制杀死线程”,而是让工作线程主动检查是否被请求停止,并自行清理后退出。它解决的是传统 std::thread::join() 前无法安全等待、或靠布尔标志轮询带来的竞态和延迟问题。
常见错误现象包括:
- 手动设置
bool stop_requested但没加std::atomic或内存序,导致读写不同步 - 线程在阻塞调用(如
std::this_thread::sleep_for、queue.pop())中卡住,无法及时响应退出信号 - 多次调用
request_stop()导致未定义行为(其实只允许调用一次,后续调用无效但不报错)
如何创建并传递 stop_token 给工作线程
关键在于使用 std::jthread —— 它自带 std::stop_source,构造时自动绑定,比裸 std::thread + 手动管理更安全。
#include <thread>
#include <chrono><p>void worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
// 做点事
std::this_thread::sleep_for(100ms);
}
// 这里做清理:关闭文件、释放资源、通知其他模块...
}</p>
<p>int main() {
std::jthread t{worker}; // 自动获得绑定的 stop_token
std::this_thread::sleep_for(500ms);
// t 会在析构时自动 request_stop(),也可手动:
// t.request_stop();
}</p></chrono></thread>
注意:
-
std::jthread析构时会自动调用request_stop()并join(),无需手动干预 - 若用裸
std::thread,必须自己构造std::stop_source,再通过get_token()获取stop_token传入,容易漏掉移动语义或生命周期管理 -
stop_token可拷贝,但轻量;stop_source不可拷贝,只能移动
如何在线程阻塞点响应 stop_request
单纯轮询 stop_requested() 在高延迟场景(如等待条件变量、IO、队列)下不及时。C++20 提供了带 stop_token 的阻塞函数重载:
-
std::condition_variable::wait和wait_for支持stop_token参数,被请求停止时会提前唤醒并返回false -
std::mutex::try_lock_until等超时函数也能配合使用 - 自定义阻塞操作(如网络 recv)需手动插入
stop_token.stop_requested()检查点,不能依赖系统调用自动感知
示例(带中断的条件等待):
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
<p>void worker(std::stop_token stoken) {
std::unique_lock lk{mtx};
// 等待 ready 或 stop 请求
cv.wait(lk, stoken, [&]{ return ready || stoken.stop_requested(); });
if (stoken.stop_requested()) {
// 清理并退出
return;
}
// 处理 ready 事件...
}</p>
性能影响很小:
-
stop_token内部是原子读,无锁开销 - 条件变量重载版本仅多一次原子检查,远低于一次虚假唤醒或额外 sleep
stop_token 和异常、RAII 清理怎么配合
std::jthread 析构时的 request_stop() 发生在任意时刻,包括栈展开期间。因此清理逻辑必须:
- 不抛异常(析构函数里抛异常会调用
std::terminate) - 使用 RAII 资源句柄(如
std::unique_ptr、自定义 guard 类)确保即使提前退出也能释放 - 避免在
stop_callback中做复杂操作(它运行在请求线程上下文,不是工作线程)
std::stop_callback 可用于注册回调,但它不是替代主线程清理的方案:
void worker(std::stop_token stoken) {
auto cb = std::stop_callback{stoken, []{ /* 仅做轻量通知,比如 set_flag */ }};
// ... 工作逻辑
}
容易踩的坑:
- 把
stop_callback当作主要清理入口,结果资源仍在工作线程栈上未释放 - 在回调里访问已析构的对象(比如
this指针) - 忘记
std::jthread析构顺序:先request_stop(),再join(),所以工作线程仍可能在运行中,此时修改共享状态要加锁
多线程退出的真正难点不在“发信号”,而在“谁在什么时候释放什么”。stop_token 只提供信号通道,资源生命周期边界必须由你亲手划清。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











