首页 › tutorials › 并行扇出批处理:吞吐翻倍实战

并行扇出批处理:让 Claude Code 吞吐翻倍

概述

当你需要让 Claude Code 同时处理几十个任务——为每个源文件生成单元测试、批量翻译文档、给每个模块写 API 说明——串行地一个接一个 claude -p 跑下来,瓶颈不是模型,而是「一次只跑一个」的等待。本教程教你用并行扇出(fan-out)+ git worktree 隔离 + 结构化输出归约这套组合拳,把批处理吞吐量提升 N 倍,同时彻底消除多个 Agent 互相踩踏文件的混乱。适合已经会用 claude -p 跑单任务、想把它工程化到批处理与 CI 流水线的开发者。

前置条件


一、为什么「串行批处理」是吞吐杀手

最朴素的批处理长这样:读一个任务列表,循环里逐个调用 claude -p。

# 串行:逐个跑,第二个任务要等第一个结束
while read -r file; do
  claude -p "为 $file 生成单元测试" --allowedTools "Read,Write" \
    --output-format text > "tests/$(basename "$file" .py)_test.md"
done < files.txt

如果单任务平均耗时 60 秒,50 个文件就要 50 分钟。而模型推理是远程并发的——你的本地 CPU 这 50 分钟里 99% 时间在空转。瓶颈从来不是计算,而是「串行等待」。

并行扇出的核心思路很简单:与其排队,不如同时开 N 个。

# 并行:同时开 4 个,总耗时 ≈ 50/4 × 60s ≈ 12.5 分钟
cat files.txt | xargs -P 4 -I {} \
  claude -p "为 {} 生成单元测试" --allowedTools "Read,Write" \
    --output-format text > "tests/$(basename {} .py)_test.md"

xargs -P 4 表示最多同时运行 4 个子进程。50 个任务会被切成持续 4 路并发,把那 99% 的空闲填满。

常见问题:

现象 原因 解决
并发开太高被限流 API 速率限制 -P 从 4 起步,按 429 错误逐步上调
成本突然飙升 每个进程独立计费 给每个任务加 --max-turns 上限
启动慢 每次都加载 hooks/skills/CLAUDE.md 加 --bare 跳过自动发现

二、第一次并行扇出:xargs 最小可用版

我们以「为仓库里每个 Python 文件生成一段模块说明」为例。先准备任务清单:

# 收集所有需要处理的文件路径
git ls-files '*.py' > tasks.txt
wc -l tasks.txt   # 比如 48 个文件

写一个单任务执行器(把复杂逻辑封装进函数,便于复用与调试):

# worker.sh —— 处理单个文件
#!/usr/bin/env bash
set -euo pipefail
file="$1"
claude -p "阅读 $file,用 5 条要点总结它的职责。只输出要点。" \
  --allowedTools "Read" \
  --output-format text \
  --bare \
  > "out/$(echo "$file" | tr '/' '_').md"

--bare 是提速关键:它跳过 hooks、skills、plugins、MCP 服务器、CLAUDE.md 的自动发现,让每次 -p 启动从「完整会话初始化」降级为「裸推理」。对只读、无状态依赖的批任务,这是免费的加速。

然后扇出:

mkdir -p out
cat tasks.txt | xargs -P 4 -I {} bash worker.sh {}
echo "完成: $(ls out | wc -l) 个结果"

预期结果:终端会看到 4 个 claude 进程同时跑,out/ 目录里陆续出现结果文件,总耗时约为串行的 1/4。

常见问题: - 结果文件互相覆盖:如果多个任务输出到同名文件,并发写会撞车。务必用文件路径(含目录)做唯一文件名,这里用 tr '/' '_' 把 src/auth/login.py 变成 src_auth_login.md。 - 某任务失败拖累整体:worker.sh 里 set -e 会让脚本在出错时退出,但 xargs 不会因此停掉其他进程——失败的进程悄悄没结果。下文用结构化输出做可观测。


三、撞车现场:为什么需要 worktree 隔离

只读批处理(生成说明、翻译)很安全,因为每个进程只读不写共享状态。但一旦任务要修改文件——批量重构、批量生成测试并落地到仓库——并行就会变成灾难:

根因:所有进程共享同一个工作目录。解决方案是给每个进程一个独立的工作目录,而 git worktree 天生为此而生。

git worktree add path branch 让你在不 clone 整个仓库的前提下,把某个分支/提交检出到另一个目录——多个目录共享同一个 .git,互不干扰磁盘上的工作文件。Claude Code 原生支持这套机制。

# 手动版:为每个任务开一个 worktree
git worktree add ../worker-wt-auth  -b feat/test-auth
(cd ../worker-wt-auth && claude -p "为 auth.py 补单元测试" --allowedTools "Read,Edit,Write,Bash")
git worktree remove ../worker-wt-auth --force

手动管理 worktree 很快就累。Claude Code 提供了 --worktree 标志,让会话自动跑在自己的隔离 worktree 里;并通过 WorktreeCreate / WorktreeRemove 两个 hook 事件把创建与清理彻底自动化。


四、--worktree 标志:一个 flag 拿到隔离目录

给 claude 加 --worktree(或在子智能体里设 isolation: "worktree"),Claude Code 会自动为这次会话开辟一个独立 worktree,会话期间所有文件操作都被限制在里面,结束后自动清理。

# 单个:自动隔离
claude --worktree -p "把 utils.py 里的 print 全部换成 logger.info" \
  --allowedTools "Read,Edit,Bash" \
  --permission-mode acceptEdits

关键点在于:这个 worktree 是临时的,会话结束即销毁。这意味着如果你想让改动「留下来」,必须在会话内 git commit,结束前把分支合并回主仓,或者显式保留 worktree。

把它和并行扇出结合,就得到了真正安全的多开模式:

cat tasks.txt | xargs -P 4 -I {} bash -c '
  claude --worktree -p "为 {} 编写 pytest 单元测试,落地到 test_ 同名文件" \
    --allowedTools "Read,Write,Bash" \
    --permission-mode acceptEdits \
    --output-format json
'

现在 4 个进程各跑各的 worktree,磁盘上互不重叠,没有 git checkout 冲突,没有测试互相干扰。

常见问题: - worktree 没被清理:异常退出(kill -9)会留下孤儿 worktree。用 git worktree prune 清理。 - 改动消失了:worktree 是临时的,没 commit 就销毁等于白干。在 prompt 里明确要求「完成后 git commit 到当前分支」,并在归约阶段把分支收集起来。


五、WorktreeCreate / WorktreeRemove:把生命周期交给 hook

官方 hooks 系统新增了两个事件,专门服务于并行隔离场景:

这里有一个极其关键、文档里写得很隐蔽的细节:一旦你定义了 WorktreeCreate hook,Claude Code 会把 worktree 的实际创建工作完全交给你——它「替换默认 git 行为」。也就是说,你的 hook 必须真正去执行 git worktree add,否则隔离目录根本不会出现。

典型用法是在 .claude/settings.json 里配置:

{
  "hooks": {
    "WorktreeCreate": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/wt-create.sh"
          }
        ]
      }
    ],
    "WorktreeRemove": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/wt-remove.sh",
            "async": true
          }
        ]
      }
    ]
  }
}

WorktreeRemove 加了 "async": true——清理是后台异步执行的,不阻塞会话退出,让批处理收尾更快。

worktree 创建脚本(注意它读 stdin 上的 JSON 上下文):

# .claude/hooks/wt-create.sh
#!/usr/bin/env bash
input=$(cat)                         # Claude Code 把事件 JSON 从 stdin 传入
branch=$(echo "$input" | jq -r '.branch // empty')
path=$(echo "$input" | jq -r '.path // empty')
branch="${branch:-wt/$(basename "$path")}"
# ⚠️ 既然定义了此 hook,必须自己真正创建 worktree
git worktree add "$path" -b "$branch" >/dev/null 2>&1
echo "{\"path\":\"$path\",\"branch\":\"$branch\"}"

清理脚本:

# .claude/hooks/wt-remove.sh
#!/usr/bin/env bash
input=$(cat)
path=$(echo "$input" | jq -r '.path')
git worktree remove "$path" --force 2>/dev/null || rm -rf "$path"
git branch -D "$(echo "$input" | jq -r '.branch')" 2>/dev/null || true

为什么要在 hook 里而不是脚本里管理?因为 hook 对所有 --worktree 调用全局生效——无论是你手动跑、CI 跑、还是子智能体 isolation: worktree 自动跑,创建与清理的逻辑只有一处,永远不会漏。


六、结构化输出与结果归约:可观测的批处理

并行跑起来的下一个痛点是可观测性:哪些任务成功?产出了什么?哪些失败了?把每个进程的输出从 text 升级为 json,就能用 jq 精确归约。

--output-format json 会让 -p 返回一个包含 session_id、result、用量等字段的 JSON 对象:

session_id=$(claude -p "Start a review" --output-format json | jq -r '.session_id')
claude -p "Continue that review" --resume "$session_id"

对批处理,我们把每个任务的 JSON 结果落盘,最后汇总:

# worker2.sh —— 结构化版,写一份带元数据的 JSON
#!/usr/bin/env bash
set -uo pipefail   # 注意去掉 -e,让失败也能记录
file="$1"
{
  echo "{\"file\":\"$file\","
  claude -p "为 $file 生成 pytest 测试。完成后 git commit。" \
    --allowedTools "Read,Write,Bash" \
    --permission-mode acceptEdits \
    --output-format json \
    | jq -c '{result: .result, cost_usd: (.cost_usd // .total_cost_usd), turns: (.num_turns // null)}'
  echo "}"
} > "out/$(echo "$file" | tr '/' '_').json" 2>/dev/null

扇出后归约成一份清单:

mkdir -p out
cat tasks.txt | xargs -P 4 -I {} bash worker2.sh {}

# 汇总:哪些成功、各自花了多少
jq -s 'map({file, ok: (.result != null), cost: .cost_usd})
       | {total: length, succeeded: (map(select(.ok)) | length),
          total_cost: (map(.cost // 0) | add)}' out/*.json

--output-format stream-json --verbose --include-partial-messages 则适合需要逐事件流式观察(工具调用、中间消息)的场景,但对「跑完归约」的批处理,json + jq 足够且更易解析。

常见问题: - 后台任务卡住进程:claude -p 里如果起了 dev server 之类永不退出的后台 shell,旧版本会无限挂起。v2.1.182 起默认 10 分钟上限,可用 CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS 调整,设 0 为不限制。 - JSON 解析失败:某个 -p 因限流没返回合法 JSON,jq 会报错。worker 里用 2>/dev/null 兜底,并在归约时用 select(.result != null) 把失败项标记出来单独重试。


七、实战案例:仓库全量单测生成器

把上面四块拼成一个完整、可运行的项目。目标:给仓库里所有 *.py 文件并行生成单元测试,每个任务隔离在独立 worktree,产出归约为一份带成本与状态的报告。

目录结构:

repo/
├── .claude/
│   ├── settings.json          # WorktreeCreate/Remove hooks
│   └── hooks/
│       ├── wt-create.sh
│       └── wt-remove.sh
├── batch/
│   ├── run.sh                 # 主驱动
│   └── worker.sh              # 单任务执行器
└── src/*.py

主驱动 batch/run.sh:

#!/usr/bin/env bash
set -uo pipefail
JOBS="${JOBS:-4}"
MODEL="${MODEL:-sonnet}"
rm -rf batch/out && mkdir -p batch/out

echo "==> 收集任务"
git ls-files '*.py' > batch/tasks.txt
echo "    $(wc -l < batch/tasks.txt) 个文件,并发 $JOBS"

echo "==> 扇出"
cat batch/tasks.txt | xargs -P "$JOBS" -I {} \
  bash batch/worker.sh {} "$MODEL"

echo "==> 归约报告"
jq -s '
  group_by(.ok) as $g
  | { succeeded: ($g | map(select(.[0].ok)) | length),
      failed:    ($g | map(select(.[0].ok | not)) | length),
      total_cost_usd: (map(.cost_usd // 0) | add),
      failures: (map(select(.ok | not) | .file)) }
' batch/out/*.json | tee batch/report.json

单任务执行器 batch/worker.sh:

#!/usr/bin/env bash
set -uo pipefail
file="$1"; model="$2"
slug=$(echo "$file" | tr '/' '_')
# --worktree: 隔离;--bare 跳过非必要加载;prompt 内要求 commit
claude --worktree --bare -p \
  "为 src/$(basename "$file") 编写 pytest 单元测试,覆盖主要分支。
   把测试写到 test_$(basename "$file" .py).py,运行 pytest 确认通过,
   然后 git commit -m 'test: add tests for $(basename "$file"'。" \
  --model "$model" \
  --allowedTools "Read,Write,Bash" \
  --permission-mode acceptEdits \
  --output-format json \
  2>/dev/null \
| jq -c --arg f "$file" \
   '{file:$f, ok:(.result!=null), cost_usd:(.total_cost_usd // 0),
     result:(.result[0:120])}' \
> "batch/out/$slug.json" || \
  echo "{\"file\":\"$file\",\"ok\":false,\"cost_usd\":0}" > "batch/out/$slug.json"

运行并对比:

# 串行基准
JOBS=1 time bash batch/run.sh
# 并行
JOBS=4 time bash batch/run.sh

典型结果:48 个文件,单任务约 40 秒,串行约 32 分钟,4 路并行约 9 分钟,吞吐提升约 3.5 倍(不到 4 倍是因为 git worktree 创建/销毁有固定开销、且有 API 限流)。batch/report.json 给出每个任务的成功状态与累计成本,失败的文件可一键重跑:

# 重跑失败项
jq -r '.failures[]' batch/report.json | xargs -P 4 -I {} bash batch/worker.sh {} sonnet

八、并行扇出与隔离工作流全景

flowchart TD
    A["任务清单 tasks.txt<br/>git ls-files '*.py'"] --> B["xargs -P 4 扇出"]
    B --> W1["Worker 进程 1"]
    B --> W2["Worker 进程 2"]
    B --> W3["Worker 进程 3"]
    B --> W4["Worker 进程 4"]

    W1 --> C1["WorktreeCreate hook<br/>git worktree add"]
    W2 --> C2["WorktreeCreate hook"]
    W3 --> C3["WorktreeCreate hook"]
    W4 --> C4["WorktreeCreate hook"]

    C1 --> R1["claude --worktree -p<br/>--output-format json"]
    C2 --> R2["claude --worktree -p"]
    C3 --> R3["claude --worktree -p"]
    C4 --> R4["claude --worktree -p"]

    R1 --> J1["out/file1.json"]
    R2 --> J2["out/file2.json"]
    R3 --> J3["out/file3.json"]
    R4 --> J4["out/file4.json"]

    R1 --> D1["WorktreeRemove hook async<br/>git worktree remove"]
    R2 --> D2["WorktreeRemove async"]
    R3 --> D3["WorktreeRemove async"]
    R4 --> D4["WorktreeRemove async"]

    J1 & J2 & J3 & J4 --> G["jq -s 归约<br/>report.json: 成功/失败/成本"]
    G --> H{"有失败?"}
    H -- 是 --> A2["重跑失败项"]
    H -- 否 --> Z["合并各 worktree 分支<br/>git merge / cherry-pick"]

九、调参与避坑清单


延伸阅读


训练营适用性评估


参考来源

  1. Run parallel sessions with worktrees — Claude Code Docs — 官方 worktree 并行隔离机制与 --worktree 标志说明
  2. Hooks reference — Claude Code Docs — WorktreeCreate/WorktreeRemove 等事件 schema、handler 类型与 async hook 配置
  3. Headless mode — Claude Code Docs — claude -p、--bare、--output-format json/stream-json、--resume 等 flag 权威参考
  4. Claude Code Batch Processing Complete Guide — /batch 命令与 claude -p 大规模批处理工程实践
  5. Git Worktrees + Claude Code: The 2026 Playbook for Running Parallel Agents — 多 Agent 并行与分支冲突治理的社区实战总结

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入