用std::weak_ptr.lock()检查任务有效性,撤回时仅销毁shared_ptr;依赖图用unordered_map存正向/反向索引以O(1)更新入度;禁用async,自建线程池并冻结依赖快照,状态迁移需mutex保护且cancelled→invalid单向强制。

如何用 std::shared_ptr 和 std::weak_ptr 管理任务生命周期,避免撤回时崩溃
任务撤回的本质是提前终止未完成的异步操作,但 C++ 没有内置“取消令牌”机制。直接 delete 正在运行的 std::thread 或销毁捕获了局部变量的 std::function,大概率触发 std::terminate 或 UAF(use-after-free)。
核心解法是让每个任务持有一个 std::weak_ptr<taskstate></taskstate>,执行体内部用 lock() 检查是否仍有效;撤回时只销毁对应的 std::shared_ptr,不碰线程本身。
- 所有任务状态(如
is_cancelled、result、依赖列表)必须封装在堆分配对象中,由std::shared_ptr持有 - 线程函数或
std::async的 lambda 中,仅通过weak_ptr.lock()获取临时强引用,检查if (!ptr) return;再继续 - 不要在线程里调用
shared_ptr.reset()或delete this—— 撤回逻辑必须在调度器主线程中统一触发
依赖图怎么建:用 std::map 还是 std::unordered_map 存任务 ID 到节点?
任务图本质是 DAG(有向无环图),每个节点需快速查入度、出度、前驱/后继。用 std::map 会引入不必要的排序开销,而 std::unordered_map 在键为整数或短字符串时平均 O(1),更合适。
但注意:依赖关系不是静态的。任务撤回后,其所有后继节点的入度要减 1;若某后继入度降为 0,应自动触发调度 —— 这要求你不能只存 “ID → 节点指针”,还得维护反向索引。
立即学习“C++免费学习笔记(深入)”;
- 正向结构:
std::unordered_map<taskid std::shared_ptr>></taskid>,用于按 ID 查任务 - 反向结构:
std::unordered_map<taskid std::vector>></taskid>存每个任务的直接后继(即 “谁依赖我”),撤回时靠它批量更新入度 - 避免用
std::vector存全部依赖边 —— 插入/删除慢,且无法快速定位某任务的所有前置任务
std::async + std::launch::deferred 能不能当任务执行器?
不能。虽然 std::async 看起来方便,但它有两个硬伤:一是 std::launch::deferred 是惰性求值,调用 get() 才执行,无法满足“提交即调度”的工作流语义;二是 std::launch::async 启动的线程无法被外部中断,也无法感知调度器的全局暂停/恢复信号。
真实生产环境必须自己管线程池,比如用 std::queue + std::condition_variable + 工作线程循环 pop()。否则依赖管理、优先级、资源限流全落空。
- 把任务包装成
std::function<void></void>放进线程池队列,执行前先检查task_state->is_cancelled.load() - 不要让任务函数直接调用
std::this_thread::sleep_for做重试 —— 这会阻塞整个工作线程;改用调度器统一管理延时任务队列 -
std::async只适合单次、无依赖、无需撤回的简单场景,混进任务图里等于埋雷
撤回一个任务时,为什么它的下游没被正确标记为“跳过”?
常见原因是依赖检查只做了一层。比如 A → B → C,撤回 A 后只把 B 标记为 skipped,但没递归传播到 C。更糟的是,如果 C 已经开始执行(因 B 的状态还没来得及更新),就会产生脏数据。
解决方式不是靠“运行时检查”,而是靠“提交时冻结依赖快照”。每个任务启动前,必须确认所有前驱状态为 done 或 skipped;一旦某个前驱变为 cancelled,整个子图应立即进入 invalid 状态,后续节点拒绝执行。
- 用原子枚举
enum class TaskStatus { pending, running, done, skipped, cancelled, invalid },其中invalid表示“因上游撤回导致本任务不可信” - 状态迁移必须受
std::mutex保护,且cancelled → invalid是单向强制转换,不可逆 - 别在
TaskNode里放std::atomic<bool></bool>类型的should_skip—— 多个并发写入会导致丢失更新
依赖图的撤回不是删除边,而是注入一种“无效性传播”信号。这个信号比数据流更关键,也更容易被忽略。










