qinghuan
← 让长任务能暂停和恢复 / 全部笔记

单元 33 / 让长任务能暂停和恢复

P2 · 按需深入L3 · 深入

在预算内拆解任务与协作

把计划写成有输入、结果和先后顺序的任务表,再让真正独立的子任务并行处理。

本篇目录 · 按需要跳转

这篇重点学什么

本篇重点:画出没有循环依赖的任务图(DAG),找出耗时最长的依赖路径(关键路径),再规定角色、预算和失败后受影响的任务。 安排固定角色的权限、依赖、通信与结果合并,判断是否需要多个 Agent。

开始读正文 ↓
学过相近内容?查看哪些可以跳过

两条路线有些基础相同。已经学会的部分可以跳过,另一条路线只用来补你当前需要的内容。

工作流与决策搜索

共同基础任务、状态、依赖与预算是共同语言,固定调度与搜索未知动作分别学习。

另一条路线补充什么?

比较 Beam Search 与 MCTS / UCT 怎样选择候选,推演尝试新选择(探索)、利用已有好选择与搜索预算之间的关系。

多 Agent 协作

共同基础角色之间会交换信息并共享结果;工程编排不等于联合策略训练。

另一条路线补充什么?

先观察队友改变策略后,旧经验为什么可能失效;再学集中训练、分散执行(CTDE)和 COMA,检查各成员的动作对策略更新与合作收益分别有什么影响。

读完这篇,你能做什么

能比较单代理与受控协作的同组任务结果,报告质量、耗时、调用量与冲突处理。

学习理由、准备要求与知识点

为什么学它

多个 Agent 是否有帮助,要与单个 Agent 做同组任务比较,并限制总预算;任务拆分不当可能增加成本和错误。

先读这些内容

知识点清单

  • 单代理基线
  • 任务独立性和依赖
  • 结构化计划
  • 依赖无环检查
  • 有限并发和全局预算
  • 子任务最小上下文
  • 证据合并与冲突保留
  • 共享写入和重复副作用

运行前先确认完整算法使用 Java 17 标准库;JDK 源码基线为 OpenJDK 17 GA。示例是内存中的机制模型,外部服务接入需要另外配置和验证。

01先弄懂它是什么

有些工作可以交给多个 Agent 同时做,例如分别查找互不依赖的两类资料,再汇总结果。前提是每项工作能独立推进,最后也知道怎样合并。把一个含糊问题复制给更多模型,可能只会增加重复、分歧和成本。先让单个 Agent 跑通并测出问题,再判断哪些部分确实适合拆开。

能执行的计划,需要写明每个任务用什么输入、交什么结果、依赖谁、最多花多少预算。例如 B 必须先读 A 找到的证据,就不能假装它们能同时独立完成。每个子任务只获得自身需要的上下文和权限,最后由专门的汇总步骤保留证据,并说明冲突和失败,而不是简单拼接所有回答。

控制器要统一管住同时跑几个任务、总共能花多少预算、怎样取消,以及谁负责写共享资源。例如每个子任务都没超过各自十次调用的上限,十个子任务合起来仍可能超出父任务预算,因此父任务要维护总账。若多个 Agent 都想改同一条记录或同一文件,要先指定写入负责人,或要求写入时核对版本;不能各自改完再指望模型自动消除冲突。

文中这几个词,先认一认

依赖DAG
表示任务先后关系的有向无环图。沿箭头往后走的任务,必须等前面的依赖完成,图中不能绕回自己。
关键路径
按依赖关系必须串行完成、耗时最长的那条路线,它决定了任务总时间能缩短到什么程度。
全局预算
所有子任务合起来共同遵守的时间、调用次数和用量上限。

还有陌生词?到术语小抄里看一个具体例子 ↗

02看看它怎么工作

本题假设任务耗时已知、依赖固定且工作者无限,计算最早完成时间。没有前驱的任务从零开始,其他任务开始时间等于所有前驱完成时间的最大值,完成时间再加自身耗时。依赖最长路径决定这个理想条件下的总耗时下界。

任务A耗时三、B耗时五,C耗时二且依赖A和B,A与B并行后C在五开始,总耗时七,不是三加五加二。若实际只有一个工作者,总时间可能十;算法的七不能被宣传为任意机器资源下的可实现最优调度。

把每个任务还有几个前置任务没完成记下来,这个数叫入度。处理完一个任务,就把它的完成时间告诉后面的任务,并把相应计数减一;降到零,才说明所有依赖都处理完,可以加入待执行队列。这个过程就是拓扑排序。若最后仍有任务没有被处理,说明依赖绕成了环,应在执行前拒绝。另记下哪个前驱最晚完成,就能回溯一条关键路径,判断缩短哪项任务可能影响总时间。

正在加载流程图…

图中状态只描述本篇可执行机制;业务调用仍需由外层应用负责。
查看流程图源码(Mermaid)
flowchart TD
  P["结构化计划与全局预算"] --> A["独立资料任务 A"]
  P --> B["独立资料任务 B"]
  A --> M["证据与冲突合并"]
  B --> M
  M --> V["统一验证"]
  V --> W["受控提交或部分失败"]

按顺序看,每一步做了什么

  1. 用单Agent基线确定真正可拆分的独立任务。
  2. 定义各子任务输入、产出、依赖与权限范围。
  3. 检查计划无环并计算理想关键路径。
  4. 在有限工作者和共享预算下调度,传播失败与取消。
  5. 合并证据、保留冲突,再做整体质量与成本比较。

03对照实现代码

OpenJDK 17 CompletableFuture 源码 (新标签页)↗

OpenJDK 17 GA,jdk-17+35 · CompletableFuture.allOf

下方为原创 Java 教学片段,调用已核验的 JDK API 说明局部机制;不是该类原样源码,也不实现神经网络。完整程序见算法模块。

核心机制代码 · java
CompletableFuture<Void> joined = CompletableFuture.allOf(first, second);
joined.thenRun(() -> mergeValidatedResults(first.join(), second.join()));
// allOf协调已经创建的任务完成,不自动限制并发或合并业务证据。
// 一个子任务失败会影响组合完成状态;取消和资源清理仍需显式设计。

CompletableFuture.allOf创建等待所有给定Future完成的组合阶段,返回类型是Void;业务结果仍需从各自Future读取并按契约合并。

allOf不会自动启动任意业务工作,也不为每个任务分配独立权限和预算。任务如何提交、使用哪个执行器以及并发上限,都在应用控制范围内。

异常完成与业务冲突不同。任务成功返回两份互相矛盾证据时,Future状态可能完全正常,合并器仍要保留冲突并核对来源。

本篇Java算法计算DAG关键路径,没有调用线程池或模型。它提供依赖结构的理论基线,真实并发延迟还包括资源等待、网络和调度开销。

代码先调用谁,再调用谁

  1. 按依赖创建或提交受限任务
  2. 前驱Future完成
  3. allOf聚合依赖完成状态
  4. 读取并验证各结果
  5. 按证据与冲突规则合并

04动手写一个例子

DAG最早完成时间与关键路径

输入非负任务耗时和依赖边,假设无限工作者且无调度开销,输出最早总完成时间及一条关键路径;有环拒绝。Java17 Main.java运行自测。

输入 1
耗时[3,5,2],0和1都指向2
输出 1
总时间7,路径[1,2]
输入 2
两个独立任务3和5
输出 2
总时间5

拓扑入度确保任务只在全部前驱都已传播信息后处理。start[v]保存目前已处理前驱完成时间最大值,最后一个前驱完成传播时它就成为正确最早开始时间。

finish[v]=start[v]+duration[v],再用它更新各后继。时间相同采用较小前驱编号稳定选择路径,不影响最早时间本身。

最终选择完成时间最大的终点并回溯前驱。若图有环,部分节点永远不会入度归零,processed<n时拒绝,不输出一个遗漏环内任务的伪完成时间。

为什么这样写是对的任务出队时所有前驱已处理,start等于其前驱完成时间最大值;每条依赖被传播一次,保存的前驱形成一条达到该时间的有效路径。

时间 O(V+E)额外空间 O(V+E)

邻接表、入度、开始时间与前驱数组按图规模存储,拓扑队列每节点一次,边每条一次;不含实际任务执行。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

Main.java · 完整可运行程序 · Java
import java.util.*;
public class Main {
    record Plan(long finish,List<Integer> path){}
    static Plan critical(long[] duration,int[][] edges){
        int n=duration.length;for(long d:duration)if(d<0)throw new IllegalArgumentException("duration");
        List<List<Integer>> g=new ArrayList<>();for(int i=0;i<n;i++)g.add(new ArrayList<>());
        int[] indegree=new int[n],previous=new int[n];Arrays.fill(previous,-1);long[] start=new long[n],finish=new long[n];
        for(int[] e:edges){if(e.length!=2||e[0]<0||e[1]<0||e[0]>=n||e[1]>=n)throw new IllegalArgumentException("edge");g.get(e[0]).add(e[1]);indegree[e[1]]++;}
        Deque<Integer> q=new ArrayDeque<>();for(int i=0;i<n;i++)if(indegree[i]==0)q.add(i);int processed=0;
        while(!q.isEmpty()){
            int u=q.removeFirst();processed++;finish[u]=Math.addExact(start[u],duration[u]); // 时间溢出明确失败。
            for(int v:g.get(u)){
                if(finish[u]>start[v]||finish[u]==start[v]&&(previous[v]<0||u<previous[v])){start[v]=finish[u];previous[v]=u;} // 保存最大前驱。
                if(--indegree[v]==0)q.addLast(v); // 全部依赖处理后才允许开始。
            }
        }
        if(processed!=n)throw new IllegalArgumentException("cycle");if(n==0)return new Plan(0,List.of());
        int end=0;for(int i=1;i<n;i++)if(finish[i]>finish[end])end=i;
        List<Integer> path=new ArrayList<>();for(int p=end;p>=0;p=previous[p])path.add(p);Collections.reverse(path);return new Plan(finish[end],path);
    }
    static void eq(Plan e,Plan a){if(!e.equals(a))throw new AssertionError(a);}
    public static void main(String[] args){
        eq(new Plan(7,List.of(1,2)),critical(new long[]{3,5,2},new int[][]{{0,2},{1,2}}));
        eq(new Plan(5,List.of(1)),critical(new long[]{3,5},new int[][]{}));
        eq(new Plan(0,List.of()),critical(new long[]{},new int[][]{}));
        eq(new Plan(3,List.of(0,1)),critical(new long[]{0,3},new int[][]{{0,1}}));
        try{critical(new long[]{1,1},new int[][]{{0,1},{1,0}});throw new AssertionError();}catch(IllegalArgumentException expected){}
        System.out.println("PASS 5 tests");
    }
}

这些特殊情况也要试一试

  • 无限工作者是假设,有限并发可能更慢。
  • 有环计划必须拒绝,不只忽略无法排序的任务。
  • 零耗时任务仍可能承担依赖边界。
进阶选读 · 后续再读:资源约束项目调度

场景只有少量工作者,工具还共享API限额或写锁。

做法先保留原有任务依赖,再加上现实限制:例如只有两名工作者、某个 API 每次只能承受一个调用,或写同一资源时必须排队。可以先尝试优先安排关键路径上的任务等经验规则,但它们不保证总是最优。对小例子,可用分支定界逐步排除不可能更好的排程,求一个精确结果作对照;同时记录每项任务到底等了多久资源。

复杂度一般资源约束调度可为NP-hard,不能把DAG最长路径的线性时间结论推广到有限资源最优排程。

好处与代价并发提升速度可能增加峰值成本和冲突,更多Agent也增加合并工作,需以端到端质量和延迟衡量。

05项目里怎么用

只分派具体且边界清楚的子任务,提供完成标准、允许资料和返回结构。每个子任务返回证据与不确定点,父任务合并时核对引用和冲突,不能把多数意见当作事实投票。多个模型共享相同错误来源时,一致性并不增加真实性。

共享写入应串行化或基于版本合并,同一文件、数据库记录或操作键指定所有者。子任务失败时决定整体失败、部分结果或受限重试,并传播取消。不要让父任务已结束而后台子任务继续消耗预算或修改资源。

比较单Agent与多Agent时使用相同资料和验收集,报告总调用量、关键路径、等待时间和合并质量。速度改善可能来自更多并行资源,必须同时展示资源代价,避免只用首个子任务完成时间代表整件事完成。

容易踩的坑

  • 含糊任务复制给多个Agent而没有独立产出。
  • 把无限资源关键路径当成实际有限资源最优时长。
  • 子任务预算独立设置,合计超出全局上限。
  • 多个Agent一致就认定事实已经核实。

几种做法,怎么选

表格有四列,窄屏可左右滑动查看代价和适用条件。

方案优势代价与限制适用条件
单Agent控制简单,上下文集中部分独立工作无法并行小任务与基线
受控并行子任务可缩短独立步骤等待增加资源、协调和合并成本清晰可分任务
集中合并与写入减少冲突,便于审核合并节点可能成为瓶颈多来源研究与修改计划

06检验是否读懂了

先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。

先用自己的话解释

01关键路径说明什么?

展开参考答案与推导

在固定耗时和依赖下无法并行压缩的最长路径,给无限资源最早完成时间;实际有限资源可能更慢。

02allOf会自动合并证据吗?

展开参考答案与推导

不会,它聚合Future完成状态,业务结果仍要按来源、权限和冲突规则处理。

03为何保留单Agent基线?

展开参考答案与推导

用于判断拆分是否真的改善质量或延迟,并揭示并发收益是否只是更多资源换来的。

换个情况,还会处理吗

01两个子任务都改同一记录,怎么办?

展开参考答案与推导

指定写入所有者或采用版本条件更新和明确合并,不能靠自然语言约定后任意并发覆盖。

02多个答案一致但都引用同一错文档,如何判定?

展开参考答案与推导

一致不代表独立证据,核对原始来源和版本;保留来源相关性,避免把重复信息当多重验证。

03父任务取消后子任务仍跑,哪里缺设计?

展开参考答案与推导

缺少任务树取消与资源清理,需传播停止并核对已经发出的副作用,不能仅隐藏UI。

写代码试一试

01并行例题为何是七不是十?

输入 / 条件
A3,B5并行,C2依赖两者
输出 / 预期
7

提示 C等待最大前驱时间。

展开参考答案与推导

A在三完成、B在五完成,C从五开始到七;相加十是假定A和B串行。

02有环怎样发现?

输入 / 条件
A依赖B,B依赖A
输出 / 预期
processed小于节点数

提示 入度无法全部归零。

展开参考答案与推导

拓扑队列无法处理环内节点,最终数量检查拒绝计划,而不是返回已处理部分的耗时。

03如何验证实际并发限制?

输入 / 条件
相同DAG仅一个工作者
输出 / 预期
实际时间可能10

提示 增加资源模型。

展开参考答案与推导

本算法输出理想下界,另写受限调度模拟或测量真实执行,不能把七作为必须达到的运行断言。

读完之后,独立完成

课后作业:自己做一遍

本篇共 8 道作业,先完成 6 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。

这次练什么

能比较单代理与受控协作的同组任务结果,报告质量、耗时、调用量与冲突处理。

本篇重点加练

计划合法性与共享预算是多代理调度的基本约束,按需深入组合后的失效边界。

  • 依赖无环检查
  • 有限并发和全局预算

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01原理问答

    选择一个Java笔记整理任务,写出单代理基线与拆分方案;逐个判断子任务是否独立,并列出拆分新增的协调成本。

    提交内容
    一份独立书面回答或标注图,包含自造例子、概念边界及判断依据。
    怎样算完成
    • 逐项回应题目要求,区分事实、假设与个人判断。
    • 能用自己的例子解释适用条件,记录尚未弄懂的点。

    练到的知识点单代理基线 · 任务独立性和依赖

  2. 作业 02代码推演

    输入任务依赖A→C、B→C、C→D,再加入D→B。定义计划结构,手工推演校验器处理过程,并指出应保留哪些错误定位信息。

    提交内容
    手工推演记录,写明输入约定、中间状态、最终判断;可运行时附程序对照记录。
    怎样算完成
    • 推演包含中间过程,数据形状、单位、顺序或状态前后一致。
    • 主动检查至少一个边界或异常条件,能解释手算与运行差异。

    练到的知识点结构化计划 · 依赖无环检查

综合应用

  1. 作业 03面试场景

    三个子任务共享100单位预算且最多并发2个,另有一份只允许主任务读取的资料。制定分配规则和最小上下文清单。

    提交内容
    场景分析与决策记录,包含信息缺口、约束、备选方案和验证计划。
    怎样算完成
    • 方案回应给定约束,列出至少一种替代方案及代价。
    • 每项关键结论说明所需证据,不能核实的部分明确标注。

    练到的知识点有限并发和全局预算 · 子任务最小上下文

  2. 作业 04编程实践

    实现本地假代理调度器,两个子任务给出冲突证据并重复提出同一写入动作;验证合并结果、冲突保留和副作用次数。

    提交内容
    可复现的最小程序或实验材料、自写测试、环境说明与运行记录;题目允许纸面实验时提交完整案例表。
    怎样算完成
    • 提交物按说明可以重现;测试覆盖正常、边界与失败场景。
    • 运行证据与断言能核验声明的行为,注明模拟与真实执行范围。

    练到的知识点证据合并与冲突保留 · 共享写入和重复副作用

  3. 作业 05排错实验

    注入一个永不结束的子任务与一个已失败的依赖,观察整体是否能停止;记录排队、运行、取消和预算释放的事件顺序。

    提交内容
    最小故障输入、复现步骤、观测记录、定位依据及回归用例。
    怎样算完成
    • 其他人可按记录复现故障,并区分现象与已验证原因。
    • 定位过程有观测依据,回归用例可以再次暴露原问题。

    练到的知识点任务独立性和依赖 · 依赖无环检查 · 有限并发和全局预算

  4. 作业 06编程实践

    实现允许提交“计划修订版”的小调度模拟器,初版无环且预算可行,修订版增加依赖与额外工作量;检验修订在不同运行阶段的接收规则。

    提交内容
    可复现的小程序或题目允许的离线实验、自写测试、输入和运行记录。
    怎样算完成
    • 输入、环境和运行过程可重现,测试覆盖声明的重点条件。
    • 验证目标由合同确定,模拟结果不冒充真实服务证据。

    练到的知识点依赖无环检查 · 有限并发和全局预算

进阶挑战 · 掌握前置后选做

  1. 作业 07设计评审

    用固定任务集对照单代理与多代理,预先定义质量、总成本与耗时指标,给出控制随机性和解释失败样本的方法。

    提交内容
    设计评审文档或对照实验报告,包含前置条件、方案取舍、验收证据与尚未验证的范围。
    怎样算完成
    • 比较使用一致条件与事先声明的标准,记录失败与未知结果。
    • 说明方案的适用前提、未覆盖风险及后续验证方法。

    练到的知识点单代理基线 · 有限并发和全局预算 · 证据合并与冲突保留

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 08思维拓展 · 反例构造

    为“任务图无环且每个子任务有独立Token预算,整体执行必然按期结束”设计反例,要求说明等待条件与共享资源,给出可复现事件序列。

    提交内容
    具体反例、原主张的适用前提、复现或逻辑证明及结论边界。
    怎样算完成
    • 反例满足原主张所声称的前提,确实针对其结论。
    • 说明一个反例能推翻什么、不能进一步证明什么。

    练到的知识点依赖无环检查 · 有限并发和全局预算

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗