C++20中的std::stop_token是什么?(实现协作式线程中断)

std::stop_token是C++20协作式中断机制的只读句柄,用于查询停止请求或注册回调;它不主动终止线程,需用户代码显式响应,常与std::jthread配合使用,后者自动关联std::stop_source并提供get_stop_token()。

std::stop_token 是 C++20 引入的协作式线程中断机制的核心组件,它本身**不主动终止线程**,而是一个轻量级句柄,用于查询是否已请求停止(stop_requested())或注册回调(register_callback)。真正的中断逻辑必须由用户代码显式检查并响应。

std::stop_token 怎么和 std::jthread 配合使用?

std::jthread 是 C++20 对 std::thread 的增强,构造时自动关联一个 std::stop_source,其 get_stop_token() 方法返回对应的 std::stop_token。这是最常见、最安全的获取方式。

  • 每个 std::jthread 拥有独立的 std::stop_source,互不干扰
  • 调用 jthread.request_stop() 会触发所有已注册的回调,并使后续 token.stop_requested() 返回 true
  • 线程函数内应定期检查 token.stop_requested(),或在阻塞点(如 std::this_thread::sleep_for)后立即检查
void worker(std::stop_token token) {
    while (!token.stop_requested()) {
        // 做工作...
        std::this_thread::sleep_for(100ms);
    }
    // 清理资源
}

int main() {
    std::jthread t{worker};
    std::this_thread::sleep_for(500ms);
    t.request_stop(); // 安全触发停止
}

std::stop_token.register_callback 为什么可能不执行?

回调函数仅在「停止请求已发出」或「后续发出停止请求」时被调用,且只执行一次。常见失效原因:

  • 回调对象(如 lambda)生命周期结束前未注册成功:必须确保 std::stop_callback 对象(或持有它的变量)在停止发生前持续有效
  • 注册时停止已请求(token.stop_requested() == true),则回调会**立即同步执行**,若此时栈已展开或资源已释放,可能引发未定义行为
  • 回调抛出异常:C++20 规定,若回调抛出异常,程序直接调用 std::terminate()
  • 多个回调注册顺序不确定,不保证执行顺序

std::stop_token 和 std::stop_source、std::stop_callback 的关系是什么?

三者构成完整协作中断链:

  • std::stop_source:唯一能发起停止请求的对象,通过 request_stop() 触发
  • std::stop_token:只读视图,可多次拷贝,用于查询或注册回调;拷贝开销极小(通常只是指针)
  • std::stop_callback:模板类,封装回调函数对象;构造时绑定 stop_token 并注册,析构时自动注销

注意:std::stop_token 无法反向获取 std::stop_source,这是有意设计——避免误传或滥用停止控制权。

std::stop_token 在非 jthread 场景下怎么手动管理?

可以独立创建 std::stop_source,再派生 std::stop_token,适用于自定义线程池、异步任务等场景:

std::stop_source source;
std::stop_token token = source.get_token();

// 注册回调(注意生命周期!)
auto cb = std::stop_callback{token, []{ std::cout << "stopped!\n"; }};

// 稍后触发
source.request_stop(); // 此时 cb 被调用

但务必注意:如果 source 先销毁,而 token 仍被使用(例如传入其他线程),token.stop_requested() 行为是未定义的。因此,推荐将 stop_source 与受控生命周期的对象(如 task 类)绑定,而非裸全局变量或临时对象。

最容易被忽略的一点:协作式中断不是“杀线程”,而是“通知线程该停了”——所有检查点、所有资源清理路径、所有阻塞调用后的恢复点,都得你亲手写 if (token.stop_requested()) return; 或类似逻辑。没有银弹,只有责任移交。