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