std::stop_callback是C++20协作取消机制中绑定到stop_token的单次执行清理回调,仅在关联stop_source调用request_stop()且callback实例未析构时生效;典型场景为异步任务被外部取消时安全释放文件句柄、内存映射等独占资源。

std::stop_callback 是什么,它在什么场景下才真正生效
std::stop_callback 不是“注册一个回调然后等它自动触发”的通用机制——它只在关联的 std::stop_source 被显式调用 request_stop() 时触发,且仅对**尚未析构**的 callback 实例生效。常见误解是把它当作类似 std::atexit 的全局钩子,但实际它绑定到单个 std::stop_token 生命周期,一旦 token 失效(比如所属 std::jthread 已退出),回调就再不会执行。
典型适用场景:你正在写一个可取消的异步任务(例如用 std::jthread 启动的循环),需要在任务被外部请求停止时,安全释放独占资源(如文件句柄、内存映射、网络 socket)。
关键点:
-
std::stop_callback对象必须保持存活,直到 stop 请求发生;局部变量声明容易导致提前析构,回调永不执行 - 它不替代 RAII,而是补充:RAII 处理正常路径,
stop_callback处理“中途被砍断”的异常取消路径 - 回调函数内禁止抛异常(C++20 标准要求 noexcept),否则程序直接终止
如何正确构造并持有 std::stop_callback 实例
最易出错的是生命周期管理:把 std::stop_callback 声明为栈上局部变量,结果函数返回后它就析构了,后续 request_stop() 完全无效。
立即学习“C++免费学习笔记(深入)”;
推荐做法:
- 在任务对象(如类成员)中声明为
std::stop_callback成员变量,确保其生存期覆盖整个任务生命周期 - 若用
std::jthread,可在其构造时传入带std::stop_token参数的函数对象,在函数体内捕获并构造std::stop_callback,但注意该 lambda 必须按值捕获 token,否则 token 可能失效 - 避免使用
std::shared_ptr<:stop_callback></:stop_callback>—— 它不解决根本问题,反而引入共享所有权复杂度
示例(安全写法):
class Worker {
std::stop_callback cb_;
public:
explicit Worker(std::stop_token st)
: cb_(st, []{ /* 清理逻辑 */ close(fd_); }) {}
};这里 cb_ 是成员变量,只要 Worker 活着,回调就有效;st 由 std::jthread 提供,保证与线程生命周期一致。
清理逻辑里不能做什么
std::stop_callback 的回调函数必须是 noexcept,编译器会强制检查。这意味着:
- 不能调用可能抛异常的函数,例如
std::vector::push_back()、std::string::assign()、任何未标记noexcept的自定义函数 - 不能进行阻塞 I/O(如
read()、write()、pthread_join()),因为此时线程正处在“被请求停止”状态,继续等待可能死锁 - 不能访问已被其他线程销毁的对象(例如通过裸指针访问已
delete的资源),callback 执行时机不可控,需确保所有依赖对象仍有效
安全操作仅限于:
- 关闭文件描述符(
close()) - 释放 malloc 分配的内存(
free()),但不建议用delete(除非确定分配器状态稳定) - 原子标志置位(
flag_.store(true, std::memory_order_relaxed)) - 调用已知
noexcept的函数,如munmap()、epoll_ctl(..., EPOLL_CTL_DEL, ...)
为什么 std::jthread + std::stop_callback 组合比手写 cancel flag 更可靠
手动轮询 std::atomic<bool></bool> flag 有竞态和延迟问题:线程可能刚读完 false 就被取消,但还没来得及检查下一次;而 std::stop_source::request_stop() 是原子广播,所有关联的 std::stop_token 立即感知,并触发已注册的 std::stop_callback(只要它还活着)。
更重要的是语义清晰性:
-
std::jthread析构时自动调用request_stop()并join(),无需额外判断 -
std::stop_callback明确区分“取消响应逻辑”和“主任务逻辑”,避免把清理代码散落在循环条件或异常分支里 - 多个 callback 可共存于同一 token,适合模块化资源管理(例如每个子组件注册自己的清理回调)
但要注意:std::stop_callback 不提供执行顺序保证。若注册了多个 callback,它们执行顺序未指定,不能假设 A 一定在 B 之前运行。如有依赖,应合并进同一个 callback 或用外部同步机制。
真正难的不是写几行 callback,而是设计好资源所有权边界、确保 token 和 callback 的生存期严格对齐、并在清理路径中避开所有可能中断或抛异常的操作。这些细节漏掉一个,取消就变成资源泄漏或 crash。


















