C++中std::stop_callback怎么注册停止回调_C++20线程同步高级用法【并发】

2次阅读

std::stop_callback构造即绑定且仅执行一次,必须用有效Token、值捕获或共享指针避免悬垂,不可替代主动轮询和join。

C++中std::stop_callback怎么注册停止回调_C++20线程同步高级用法【并发】

std::stop_callback 构造时必须传入有效的 std::stop_token

它不是“注册”出来的,而是构造即绑定——std::stop_callback 对象一创建,回调就已关联到指定 std::stop_token,且不可更改。一旦对应线程std::jthread 调用 request_stop(),且该 token 处于可停止状态,回调就会在**该 callback 对象析构前被调用一次**(仅一次,且不保证调用线程)。

常见错误现象:std::stop_callback cb(token, []{...});token 来自已结束的 std::jthread,或本身是默认构造的空 token(std::stop_token{}),此时构造会抛 std::system_error,错误信息为 "Operation not permitted"

  • 使用场景:通常在 std::jthread 启动的函数内部,用其自带的 std::this_thread::get_stop_token() 构造
  • 不要跨线程传递 token 后再构造 callback——token 本身是线程安全的,但若源线程已结束,token 可能已失效
  • callback 的生命周期必须严格长于它所依赖的资源(比如 Lambda 捕获的局部对象),否则回调执行时可能访问悬垂指针

lambda 捕获方式决定能否安全访问外部变量

捕获不当是崩溃高发区。callback 执行时机不确定,可能在线程已退出、已销毁后触发,所以绝不能用 [&] 或捕获局部变量的引用。

正确做法只有一种:值捕获所有需要的数据,或确保捕获的是上长期存活的对象(如 shared_ptr)。

立即学习C++免费学习笔记(深入)”;

  • [val = local_var]() { use(val); } —— 安全,拷贝副本
  • [ptr = std::make_shared<int>(42)]() { *ptr = 0; }</int> —— 安全,共享所有权
  • [&local_ref]() { local_ref++; } —— 危险!local_ref 很可能已析构
  • [this]() { this->flag = true; } —— 危险!除非 this 是动态分配且生命周期明确长于 jthread

callback 不是线程取消钩子,也不替代 join

它只是“通知”,不是“中断”。std::stop_callback 触发后,线程不会自动暂停或退出;你仍需在循环中定期检查 token.stop_requested(),并主动返回或 break

性能影响极小:构造/析构是常数时间,无锁;回调调用本身无同步开销,但执行内容若有锁或 IO,则由你负责控制。

  • 别指望靠 callback 清理资源后线程就“干净退出”——必须显式 join() 或让 std::jthread 析构时自动 join
  • callback 内不要调用 std::this_thread::sleep_for 等阻塞操作,可能拖慢 stop 流程
  • 多个 std::stop_callback 绑定同一 token 时,调用顺序未指定,不可依赖先后

std::jthread 析构时自动 request_stop,但 callback 不一定来得及执行

std::jthread 析构时若仍在运行,会先调用 request_stop(),再 join()。这看似“自动”,但有个关键窗口:如果 callback 正在执行,而主线程已走到 jthread 析构末尾,callback 对象可能已被销毁,导致未定义行为。

根本原因:callback 对象通常声明在函数栈上,其生命周期与作用域绑定,而 stop 请求和回调执行是异步的。

  • 最稳妥写法:把 std::stop_callback 声明为函数内 Static,或放在 std::shared_ptr 里管理
  • 更推荐:把清理逻辑直接写进线程函数主体的循环末尾,用 if (token.stop_requested()) break; 主动退出,callback 只做轻量级兜底(比如置标志位)
  • 别在 callback 里调用 token.stop_requested()——它此时必然为 true,但再查没意义,还多一次原子操作

真正难处理的,是 callback 执行期间依赖的资源生命周期和线程退出顺序的耦合。这里没有银弹,只有显式管理。

text=ZqhQzanResources