跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

c++20的std::stop_token和std::jthread如何优雅地中断线程? (协作式取消)

std::jthread构造时自动绑定stop_token,线程函数直接接收std::stop_token参数即可;析构时自动request_stop()并join(),但需线程函数主动轮询stop_requested()以响应取消。 std::jthread 构造时自动绑定 stop_token,无需手动传递 std::jthread 在构造时会自动生成一个
std::stop_source
,并把对应的
std::stop_token
传给线程函数——这是它比
std::thread
更“优雅”的起点。你不用显式创建
std::stop_source
,也不用担心忘记把 token 传进去。 常见错误是仍像用
std::thread
那样手动管理 token,结果重复构造或丢失绑定: 误写
std::jthread t{[](std::stop_token){});
:虽然能编译,但丢掉了自动绑定的语义,token 实际来自 jthread 内部,不是你传的那个 试图在构造后调用
t.get_stop_source().request_stop()
前,先用
t.get_stop_token()
去轮询——可行,但没必要,因为线程函数已天然持有有效 token 正确做法就是直接让线程函数接受
std::stop_token
参数:
std::jthread t{[](std::stop_token st) { while (!st.stop_requested()) { // 做工作... std::this_thread::sleep_for(100ms); } // 自动调用 request_stop() 并 join() }};
在循环中用 stop_token::stop_requested() 检查取消请求
std::stop_token::stop_requested()
是轻量、无锁、线程安全的检查方式,适合高频轮询。它不阻塞,也不抛异常,只返回
bool
,符合协作式取消“主动让出”的设计哲学。 立即学习 “ C++免费学习笔记(深入) ”; 注意几个关键点: 不要用
st.stop_possible()
判断是否能取消——它只表示 token 是否关联了 source,几乎总是 true,对逻辑无实质帮助 避免在 long-running 系统调用(如
read()
,
accept()
)中只靠
stop_requested()
轮询,会造成 busy-wait;应配合可中断的等待机制(见下一条) 如果线程正在等待某个条件变量,需在
wait
或
wait_for
中传入 token,否则可能错过取消信号 例如,用
std::condition_variable::wait_until
+
std::stop_token
实现可中断等待: C函数速查手册(CHM版) C函数速查手册(CHM版) 下载
std::jthread t{[](std::stop_token st) { std::mutex m; std::condition_variable cv; bool ready = false; std::unique_lock lk{m}; while (!ready && !st.stop_requested()) { if (cv.wait_until(lk, std::chrono::steady_clock::now() + 1s, [&]{ return ready; })) { break; } } }};
std::jthread 析构时自动 request_stop() + join(),但需确保线程函数响应 token 这是
std::jthread
最省心的特性:离开 作用域 时,它会自动调用内部
stop_source.request_stop()
,然后阻塞等待线程退出。但前提是——你的线程函数必须定期检查
stop_token
,否则它会永远卡在某处,导致析构 hang 住。 典型陷阱: 线程函数里没检查 token,或者只在循环开头检查一次,之后就进入不可中断的长耗时操作(如大数组排序、文件读取) 使用了不支持 cancellation 的第三方库调用(如某些 C 风格网络 API),且未设超时或封装中断逻辑 在持有锁时长时间等待,而锁未被设计为可被中断 解决思路不是“避免析构”,而是把取消点拆得足够细。例如,在处理大量数据时插入检查:
std::jthread t{[](std::stop_token st) { for (int i = 0; i < 1000000; ++i) { process_item(i); if ((i % 1000 == 0) && st.stop_requested()) { break; } } }};
需要外部触发取消?用 get_stop_source() 获取可写句柄 当线程启动后,你想从其他地方(比如主线程、信号 处理器 、UI 事件)主动请求停止,就得拿到它的
std::stop_source
。jthread 提供
get_stop_source()
成员函数,返回一个可拷贝、可移动的句柄。 注意事项:
get_stop_source()
返回的是副本,调用
request_stop()
是线程安全的,但多次调用无副作用(只会生效第一次) 不能跨线程保存
std::stop_token
后再拿去调用
request_stop()
——token 只读,只有 source 才能发起请求 如果你需要多个线程监听同一取消信号,应共享同一个
std::stop_source
,而非多个 jthread 各自独立 示例:主线程在特定条件下提前终止工作线程:
std::jthread worker{[](std::stop_token st) { while (!st.stop_requested()) { do_work(); std::this_thread::sleep_for(50ms); } }}; // ... 一段时间后 if (some_condition) { worker.get_stop_source().request_stop(); // 安全,非阻塞 }
协作式取消真正难的不是 API 调用,而是在线程执行路径的关键节点插入恰当的检查点——尤其是那些看似“短暂”实则可能因数据规模或系统负载而变长的操作。token 本身不会中断任何东西,它只提供一个“该停了”的信号;谁来响应、何时响应、响应后如何清理,全在你自己手里。

相关文章