C++stop_token请求停止协作
C++20 stop_token:优雅协作式取消机制详解
在现代C++并发编程中,如何安全、可控地终止正在运行的异步任务,一直是开发者面临的核心挑战之一。粗暴的线程中断(如 std::thread::detach() 或信号强制终止)极易引发资源泄漏、状态不一致甚至未定义行为。C++20 引入了基于协作式取消(cooperative cancellation)的标准化机制——std::stop_token、std::stop_source 和 std::stop_callback。其中,stop_token 作为轻量级“停止请求观察者”,是实现可中断异步操作的关键入口。本文将系统解析其设计哲学、使用范式与典型实践。
协作式取消的设计思想
协作式取消强调“请求—响应”而非“强制终止”。调用方通过 stop_source 发出停止请求,被管理的执行单元(如线程、协程或任务函数)持续检查关联的 stop_token 状态,并在适当时机主动退出。这种模式确保了资源清理的确定性:析构函数得以正常执行,锁可被释放,内存能被回收,文件句柄可被关闭。
stop_token 本身不可变、无状态,仅提供对底层停止状态的只读视图。它不持有任何资源,可自由拷贝、传递,甚至跨线程共享——这是其高效与安全的基础。
核心组件关系
std::stop_source:拥有停止状态的唯一所有者,提供request_stop()方法发起请求。std::stop_token:由stop_source.get_token()获取,用于轮询或注册回调。std::stop_callback<Callback>:构造时绑定回调函数,当停止请求发生时自动触发(仅一次)。
三者共同构成一个松耦合、低侵入的取消通知链。
基础用法示例
以下是一个带取消支持的长时间循环任务:
#include <iostream>
#include <thread>
#include <chrono>
#include <stop_token>
void worker_task(std::stop_token stoken) {
for (int i = 0; i < 100; ++i) {
// 每次迭代前检查是否收到停止请求
if (stoken.stop_requested()) {
std::cout << "Worker: stopping at iteration " << i << "\n";
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Worker: iteration " << i << "\n";
}
std::cout << "Worker: completed all iterations\n";
}
int main() {
std::stop_source source;
std::thread t(worker_task, source.get_token());
// 主线程等待2秒后请求停止
std::this_thread::sleep_for(std::chrono::seconds(2));
source.request_stop();
if (t.joinable()) {
t.join();
}
}
该示例展示了最简协作流程:子线程定期轮询 stop_requested(),主线程通过 stop_source 触发全局停止信号。注意 stop_token 通过值传递,符合其轻量设计目标。
使用 stop_callback 实现自动响应
对于需在停止瞬间执行清理逻辑的场景,stop_callback 提供了更优雅的注册机制:
#include <iostream>
#include <thread>
#include <memory>
#include <stop_token>
void worker_with_cleanup(std::stop_token stoken) {
auto resource = std::make_unique<int[]>(1024); // 模拟动态资源
// 注册停止回调:自动释放资源
std::stop_callback callback(stoken, [&resource]() {
std::cout << "Cleanup: releasing resource\n";
resource.reset(); // 显式释放
});
for (int i = 0; i < 50; ++i) {
if (stoken.stop_requested()) {
std::cout << "Worker: early exit at " << i << "\n";
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int main() {
std::stop_source src;
std::thread t(worker_with_cleanup, src.get_token());
std::this_thread::sleep_for(std::chrono::milliseconds(800));
src.request_stop();
if (t.joinable()) {
t.join();
}
}
stop_callback 的生命周期绑定于其所在作用域;一旦 stop_token 关联的停止状态变为 true,回调即被同步调用(在发出 request_stop() 的线程上下文中),确保清理时机可控。
与 std::jthread 的天然集成
C++20 同时引入了 std::jthread —— 自动 join 的线程封装,其构造函数原生支持 stop_token 参数,使取消集成近乎零成本:
#include <iostream>
#include <thread>
#include <stop_token>
void jthread_worker(std::stop_token stoken) {
while (!stoken.stop_requested()) {
std::cout << "Working...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
std::cout << "jthread_worker: stopped gracefully\n";
}
int main() {
std::jthread jt(jthread_worker); // 自动获取并传递 stop_token
std::this_thread::sleep_for(std::chrono::seconds(1));
jt.request_stop(); // jthread 提供便捷接口
}
std::jthread 在析构时会自动调用 request_stop() 并 join(),彻底规避了“忘记 join 导致程序挂起”的常见错误。
注意事项与最佳实践
- 避免频繁轮询:在高频率循环中,过度调用
stop_requested()可能影响性能。应结合std::condition_variable或std::future::wait_until等阻塞机制,在等待点统一检查。 - 线程安全保证:
stop_token::stop_requested()是无锁且线程安全的,但其返回值仅代表“某一时刻”的快照;业务逻辑仍需确保自身状态一致性。 - 不可逆性:一旦
request_stop()被调用,停止状态永久为 true,stop_token不可重置。如需多次取消/重启,应创建新的stop_source。 - 异常安全:
stop_callback构造可能抛出std::bad_alloc(若内存不足),应在关键路径中考虑异常处理。
结语
std::stop_token 并非万能中断开关,而是 C++20 为并发生态注入的一套清晰契约:它不替代程序员对资源生命周期的责任,却以最小侵入代价赋予任务自我终结的能力。从手动轮询到自动回调,从裸线程到 jthread,这一机制层层抽象,始终服务于一个目标——让异步代码更可预测、更易维护、更贴近现实世界的协作逻辑。掌握 stop_token,是迈向稳健 C++ 并发编程不可或缺的一步。

