← 返回
AI技术

Claude Code 并行子代理模式:扇出、扇入与同步策略实测

✍️ zhirenhun 📅 2026/10/18 👁 104 阅读 ⏱ 30 分钟
Claude Code 并行子代理模式:扇出、扇入与同步策略实测

Claude Code 并行子智能体模式:扇出、扇入及实际有效的同步策略

本文在人工监督和审阅下,得到了 AI 的协助撰写。

大多数并行子智能体实现之所以失败,是因为团队把它们当作独立的工作线程,而实际上它们是共享同一代码库的相互依赖进程。其结果是重复工作、上下文污染以及合并冲突,耗时甚至超过串行执行。

团队常忽略的模式是带显式协调的有限并发。当你派出五个子智能体去分析单体仓库的不同部分时,每个会话在分叉时看到的文件系统快照各不相同。若没有同步屏障和合并策略,它们的输出会出现偏差。编排者最终只能调和相互矛盾的建议,或者悄悄丢掉一半的工作。

解决方案是一种三阶段模式:带有限槽位的扇出、在非重叠路径上的隔离工作,以及具有确定合并顺序的扇入。编排器会强制并发限制(通常为三到五个子智能体),分配互不重叠的文件集合,并在执行任何写入之前按依赖顺序收集结果。这能消除竞态条件,使并行工作实际上比串行更快。

这张图描述了一个orchestrator强制3个子agent(A、B、C)按照依赖顺序合并成一个单一的、协调的架构

这一点很重要,因为在合理的 token 预算内,处理大规模重构或多包分析只有并行子智能体这一种方式。串行执行大约在 200K token 时会遇到上限。通过划分工作,并行模式能让你处理 1M+ token 级别的代码库,但前提是你能够控制协调开销。

要点总结

  • 阶段边界的同步屏障能够防止竞争条件,且在独立工作时不阻塞并行。
  • 实际并行模式可使单体仓库分析的墙钟时间降低 60‑70%,但前提是合并开销不超过总执行时间的 15%。
  • 扇出模式:同时派遣多个子智能体

    扇出模式一次派遣多个子智能体,各自负责整体任务的独立片段。这里的失效模式很隐蔽但代价高昂:在没有明确边界的情况下启动十个子智能体去“analyze the codebase”,会产生十个重叠的上下文。每个会话读取共享文件、提出冲突更改,编排者在协商上花费的时间超过并行所节省的时间。

    正确的模式在派遣前会施加三个约束。首先,编排者根据 token 预算和任务复杂度计算有限的并发上限。三到五个槽位是大多数单体仓库工作的测试范围。其次,编排者将输入空间划分为不相交的集合:包、模块、功能目录或测试套件。第三,每个子智能体会收到一个明确的作用域,以防止重叠。编排者维护一个待处理任务队列,仅在有空闲槽位时才释放新工作。

    这种结构可防止上下文污染,因没有两个子智能体会读写重叠文件。当子智能体 A 在 src/auth 上工作,子智能体 B 在 src/api 上工作时,它们的文件读取是相互隔离的。编排者会跟踪哪些路径正在进行中,并在冲突工作完成前阻止可能破坏不相交保证的任务。

    token 预算约束很重要,因为每个子智能体会话都携带自己的上下文窗口。如果在一个 200K token 的代码库上同时派遣十个子智能体,则会并行消耗 2M tokens。API 速率限制和成本控制都会失效。有限槽位使总活跃上下文可预测:五个子智能体每个 40K tokens,合计 200K tokens 同时使用,这通常在团队配额范围内。

    这张图是用绿色和白色条纹的背景,呈现出一系列的立体立方体

    并行子智能体编排仪表盘

    编排器实现使用简单的状态机。队列是待处理范围的优先级列表。活跃集合跟踪正在运行的子智能体会话。当一个子智能体完成时,编排器将该槽位标记为可用,并从队列中拉取下一个任务。转换逻辑确保你永远不会超过槽位限制,也不会重复预订文件路径。

    扇入模式:收集并合并子智能体结果

    扇入是大多数并行实现出问题的地方。编排器从已完成的子智能体那里接收五组结果,需要产出一个连贯的单一输出。天真的做法是将所有结果拼接起来,交给最终的合并会话。这会失败,因为合并复杂度随输入数量的平方增长。五份 10K token 报告会变成一个 50K token 合并任务,最终会话在不丢失一半建议上下文的情况下无法处理。

    工作模式是依据依赖顺序收集并进行增量合并。编排器维护原始范围的依赖图。当 src/ui 依赖 src/api 中的类型时,编排器先收集 src/api 结果,将这些更改应用到工作树,然后在更新后状态下合并 src/ui 结果。这将 N 路合并转换为 N 个顺序的 2 路合并,每个都能轻松放入上下文。

    interface SubagentResult {
      scope: string;
      changes: FileChange[];
      summary: string;
      dependencies: string[];
    }
    
    async function fanInResults(
      results: SubagentResult[],
      depGraph: DependencyGraph
    ): Promise<MergedOutput> {
      const sorted = topologicalSort(results, depGraph);
      let workingTree = await loadBaseTree();
      const mergedChanges: FileChange[] = [];
    
      for (const result of sorted) {
        // Apply changes to working tree in dependency order
        const resolved = await resolveConflicts(
          result.changes,
          workingTree
        );
        workingTree = await applyChanges(workingTree, resolved);
        mergedChanges.push(...resolved);
      }
    
      return {
        changes: mergedChanges,
        tree: workingTree,
        summary: await generateSummary(mergedChanges)
      };
    }
    

    resolveConflicts 函数是关键所在。当 Subagent A 和 Subagent B 同时修改同一条 import 语句时,解析器会依据依赖顺序选出胜者。如果 A 在拓扑顺序中先运行,其修改得以保留,而 B 的重叠编辑被丢弃。另一种方案是进行合并会话,读取两个提案并合成一个解决方案,但这会消耗 token 并增加延迟。依赖顺序免费提供了一种确定的解决策略。

    摘要生成步骤在所有更改应用后进行。这确保最终摘要反映实际的合并状态,而不是各子智能体摘要的简单相加。每个子智能体摘要描述了它在隔离状态下所做的更改。编排器摘要描述了在所有合并之后最终代码库中发生的变化,这就是读者需要审查的内容。

    换句话说,fan-in 并不是简单的拼接操作。fan-in 是一种图遍历,它按照拓扑顺序应用更改,并在最后生成一个权威的摘要。这两种方法之间的区别,相当于一个能正常工作的并行系统与产生垃圾的系统之间的区别。

    同步策略:在不产生竞争条件的情况下协调并行工作

    同步屏障可以防止并行执行期间出现竞争条件。团队遇到的失败模式是对共享状态的并发写入:两个子智能体同时更新 package.json 依赖项,第二次写入会静默覆盖第一次写入。编排器无法知道哪个版本获胜,最终状态具有非确定性。

    工作模式采用基于阶段的执行方式,并使用显式屏障。第一阶段为只读分析。所有子智能体并行执行,读取文件并生成建议,而不进行任何写入。编排器会阻塞第二阶段,直到第一阶段的所有工作完成。第二阶段为写入执行。编排器按照第一阶段的依赖顺序应用更改,且不允许新的读取操作。由于读取和写入永不重叠,这消除了读写和写写冒险。

    屏障的实现是一个信号量,用于计算已完成的子智能体数量。当计数达到已调度会话的总数时,编排器会释放下一阶段。在阶段之间,编排器可以注入验证步骤:运行测试、检查类型正确性,或在继续之前确认不存在未提交的更改。

    基于锁的协调是一种细粒度同步的替代模式。协调器维护一个以文件路径为键的锁表。在子代理读取或写入文件之前,它会获取读锁或写锁。读锁是共享的,写锁是互斥的。这样可以在同一文件上并发读取,同时将写入序列化。缺点是锁竞争开销:如果五个子代理都需要读取 tsconfig.json,它们都会成功;但如果它们都需要写入该文件,则有四个会被阻塞。

    在生产环境中,权衡在于使用基于阶段的屏障处理粗粒度任务(如分析独立包),以及使用基于锁的协调处理细粒度任务(如更新共享配置文件)。关键在于在协调器代码中明确同步策略。通过“希望子代理不会冲突”的隐式协调会导致生产环境出现非确定性故障。

    结束时的验证阶段会捕获漏掉的同步错误。所有写入完成后,最终的子代理会对合并结果进行类型检查、 linting 和测试。如果验证失败,协调器可以回滚到写入前的状态,并在更严格的屏障下重试。这样使同步错误变得可见,而不是隐藏。

    实际案例:有界并发代码库分析

    一个实际用例是分析一个 TypeScript 单仓库,以查找 50 个包中未使用的导出。串行执行会将所有 50 个包加载到单一上下文中,大约在第 30 个包时耗尽 token。并行执行将这些包分成五组,每组十个,并运行五个并发子代理。

    interface AnalysisTask {
      packages: string[];
      maxTokens: number;
    }
    
    async function parallelUnusedExportAnalysis(
      monorepoRoot: string,
      concurrency: number = 5
    ): Promise<UnusedExportReport> {
      const packages = await discoverPackages(monorepoRoot);
      const tasks = partitionPackages(packages, concurrency);
    
      const results = await Promise.all(
        tasks.map(task => 
          analyzePackageGroup(task, monorepoRoot)
        )
      );
    
      return mergeAnalysisResults(results);
    }
    
    async function analyzePackageGroup(
      task: AnalysisTask,
      root: string
    ): Promise<PartialReport> {
      const subagent = await spawnSubagent({
        workingDir: root,
        scope: task.packages,
        maxTokens: task.maxTokens
      });
    
      const exports = await subagent.findExports(task.packages);
      const imports = await subagent.findImports(task.packages);
    
      return {
        unused: exports.filter(exp => 
          !imports.some(imp => imp.references(exp))
        ),
        scope: task.packages
      };
    }
    

    partitionPackages 函数使用包依赖图来创建不相交的组。互相导入的包保持在同一组内,以避免跨组引用检查。独立的包被拆分到不同组中,以实现最大并行度。这种分区策略使每个子智能体任务保持自包含,同时保证正确性。

    合并步骤将部分报告合并为单一未使用导出列表。由于每个组都是不相交的,因此没有重复条目需要调和。编排器按包名称对最终列表进行排序,并生成摘要,显示哪些包含最多的死代码。总墙时是串行执行的五分之一,因为五个子智能体同时运行,且合并开销可忽略不计,因为部分结果不重叠。

    这张图片显示了一名男士站在白板前,正在用红色笔在白板上绘制一个图表

    单体仓库分析性能对比

    此模式的 token 预算是可预测的。每个子智能体处理大约十个包,每包约 8K tokens,因此每次会话约 80K tokens。五个会话并行运行时,飞行中的 token 总量为 400K。编排器自身只需极少量 token 进行协调,因此对于 50 包的单体仓库,总成本低于 450K tokens。串行执行将超过 500K tokens,并在完成前失败。

    此模式也适用于其他代码范围分析:死代码消除、依赖审计、类型覆盖率报告或测试文件生成。关键在于划分输入空间,使每个子智能体在不相交的子集上工作,且合并操作开销低。当合并复杂度开始主导时,说明划分过细,需要降低并发度或增大分区大小。

    常见陷阱:重复执行、上下文污染和合并开销

    第一个陷阱是由于范围重叠导致的重复执行。当编排器将 src/api 分配给子智能体 A,将 src/ 分配给子智能体 B 时,两个会话都会分析 src/api。编排器会收到两份关于同一文件的报告,并在冗余工作上浪费 token。解决办法是在调度时进行严格的不相交分区。如果某个范围与正在进行的任务重叠,编排器会将其放入队列,直到冲突任务完成。

    当子智能体共享可变状态时,就会出现上下文污染。如果两个子智能体同时读取同一个缓存文件,基于各自发现进行更新并写回,后写入的会覆盖前面结果,导致之前结果丢失。解决办法是使用写时复制缓存或只读共享状态并采用一次性写入的输出。每个子智能体都拥有用于中间结果的私有工作空间,只有编排器在合并后才会向共享存储写入。

    当子智能体的数量超过自然分区数时,合并开销会杀死性能。例如,对包含六个包的单体仓库使用十个子智能体进行分析,意味着有四个子智能体各处理一个包,另外六个子智能体也各处理一个包。此时编排器需要合并十个结果集,而不是原来的六个,多出的四个并未带来并行收益。解决办法是让并发数匹配分区数。如果你有六个自然分区,就运行六个子智能体,而不是十个。

    合并开销成为主导因素的阈值大约占总执行时间的 15%。如果你在并行分析上花费 85 秒,合并结果花费 15 秒,那就已经达到极限。在此点之后提高并发数,会导致合并时间增长速度快于分析时间下降速度。3‑5 子智能体规则来源于经验观察:大多数代码库具有 3‑5 个自然分区(前端、后端、共享库、测试、配置),超过这个范围会带来收益递减。

    这表明并行模式并不一定总是优于串行。当输入空间具有天然的不相交分区、合并操作相对于工作量来说成本低、并且并发限制与分区数匹配时,并行才能发挥优势。将并行强加到本质上顺序的任务上(例如依次应用一系列依赖的类型迁移)只会增加协调开销,而不会带来速度提升。

    生产模式:3‑5 子智能体规则及何时突破

    3‑5 个并发子智能体的限制是经过测试的生产默认值,适用于单体仓库分析、多包重构以及全代码库搜索任务。这个范围在并行收益与合并开销以及 token 预算限制之间取得平衡。三个子智能体是能够看到显著加速的最小数量。五个子智能体是在合并复杂度开始侵蚀收益之前的最大数量。

    在这两种情况下打破这条规则是合理的。首先,当任务可以 embarrassingly parallel 且没有合并步骤时。为 20 个独立的包生成文档可以启动 20 个子智能体,因为每个输出都是独立文件,没有交叉引用。编排器收集这 20 个 Markdown 文件并写入磁盘,无需任何合并逻辑。

    其次,当 token 预算允许更高并发且不触及速率限制时。如果团队配额是每分钟 200 万 tokens,每个子智能体消耗 50K tokens,则在达到限制前可以运行 40 个并发会话。限制从 token 预算转移到编排器复杂度。管理 40 个进行中会话需要健全错误处理、重试逻辑和进度跟踪,而 5 会话编排器则不需要这些。

    高并发场景的生产模式是分层编排。顶层编排器派生五个中层编排器,每个中层编排器管理五个子智能体。这样既保持每个编排器的活跃集合易于管理,又实现了 25 方式的并行。中层编排器负责局部汇聚并向顶层报告摘要,顶层负责最终的合并。该模式可扩展到数百个任务,而不会让单个编排器会话不堪重负。

    此模式的临界点在于协调开销超过实际工作时间。如果编排器将 60% 的墙钟时间用于调度任务,40% 用于等待子智能体,则说明出现了过度并行。解决办法是降低并发度或提高任务粒度,使每个子智能体每次调度能完成更多工作。目标是让编排器的空闲时间(等待子智能体)占总时间的 70% 以上,这表明并发在做有用的工作。

    设定并发度的实用经验法则是任务数除以四,上限为五。十个任务运行在三个子智能体上(10 / 4 = 2.5,向上取整为 3)。二十个任务运行在五个子智能体上(20 / 4 = 5,达到上限)。四十个任务在采用分层编排的情况下仍运行在五个子智能体上(顶层上限),而不是十个子智能体(否则会超过合并开销的阈值)。该经验法则使合并复杂度保持线性,编排器逻辑保持简单。

    常见问题

    如何防止两个子智能体同时修改同一文件?

    编排器在分发任务前会先划分互不重叠的文件范围,并追踪哪些路径正在处理中。如果新任务与某个进行中的范围有重叠,编排器会将其排队,直到冲突任务完成为止。写入阶段则按依赖顺序依次应用变更,确保每个文件始终只有一个会话在操作。

    子Agent执行中途崩溃怎么办?

    编排器通过 Promise 拒绝或超时检测到失败,并将该范围标记为可重试。失败的任务会带着递增后的重试计数回到队列。连续失败三次后,编排器会记录错误并继续处理剩余任务,避免单个问题范围阻塞整个工作流。

    子Agent在执行期间能互相通信吗?

    不能。子Agent是彼此隔离的会话,没有直接通信通道,所有协调都通过编排器完成。如果子Agent A 需要子Agent B 的数据,编排器会先收集 B 的结果,再把它作为输入传给 A。这样就强制保证了依赖顺序,避免竞态条件。

    如何验证并行执行的结果与串行一致?

    在所有合并完成后,运行一个最终验证子Agent。该会话会对合并后的代码做类型检查、运行测试,并将输出与已知正确的基线对比。如果验证失败,编排器可以回滚变更,改用串行执行重试,从而定位是哪次并行合并引入了错误。

    什么时候应该用串行而非并行?

    以下情况建议使用串行:任务之间存在严格的顺序依赖(迁移步骤 B 依赖已完成的步骤 A);代码库规模小到能装进单个上下文(10 万 token 以内);或者合并开销会超过总执行时间的 15%——通常出现在需要五个以上子Agent、而自然划分却不足三个的场景。

    总结:打造你的并行子Agent实践手册

    生产环境中并行子 Agent 的核心模式是有限并发、阶段式同步屏障以及依赖顺序汇聚。这三个要素消除了竞态条件、上下文污染以及会破坏天真并行实现的合并开销。

    从 monorepo 分析和多包重构的 3-5 子 Agent 规则开始。将输入空间划分为如包根目录或功能目录这样的自然边界。强制只读分析阶段后跟顺序写入阶段。按拓扑依赖顺序合并结果,而非按完成时间顺序。

    仅在无需合并步骤的 embarrassingly parallel 任务或为数百个任务实现分层编排时才突破并发限制。监控合并开销,当其超过墙钟时间的 15% 时回退。目标是让编排器空闲时间保持在 70% 以上,这表明并行在做实际工作,而不仅仅是增加协调复杂度。

    这涵盖了并行 Claude Code 子 Agent 的核心模式。在生产环境中应用这些方法,效果将立竿见影。您的 monorepo 分析任务将在墙钟时间的五分之一内完成。您的 token 预算将保持可预测。并且由于互不相交的作用域从根本上防止了它们的发生,合并冲突将消失。

    ——

    广告 广告
    🧑‍💻

    zhirenhun

    一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

    typescript ai javascript webdev