<!-- title: 并行扇出批处理：吞吐翻倍实战 -->

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

## 概述

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

## 前置条件

- 已安装 Claude Code（`claude -p "hi"` 能正常返回）
- 基本的命令行与 shell 脚本能力（会读 `xargs`、`jq`）
- 一个 git 仓库（worktree 隔离依赖 git）
- 理解 `--output-format`、`--allowedTools` 等 `-p` 模式常用 flag

---

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

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

```bash
# 串行：逐个跑，第二个任务要等第一个结束
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 个**。

```bash
# 并行：同时开 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 文件生成一段模块说明」为例。先准备任务清单：

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

写一个**单任务执行器**（把复杂逻辑封装进函数，便于复用与调试）：

```bash
# 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` 启动从「完整会话初始化」降级为「裸推理」。对只读、无状态依赖的批任务，这是免费的加速。

然后扇出：

```bash
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 隔离

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

- 4 个进程同时 `git checkout` 同一个分支 → 冲突
- 进程 A 改了 `package.json`，进程 B 同时读它 → 不一致
- 进程 C 跑 `npm test` 时，进程 D 正在改测试文件 → 假阳性失败

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

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

```bash
# 手动版：为每个任务开一个 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，会话期间所有文件操作都被限制在里面，结束后自动清理。

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

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

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

```bash
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**：当通过 `--worktree` 或 `isolation: "worktree"` 创建 worktree 时触发。
- **WorktreeRemove**：worktree 被移除时（会话退出或子智能体结束）触发。

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

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

```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 上下文）：

```bash
# .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\"}"
```

清理脚本：

```bash
# .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 对象：

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

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

```bash
# 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
```

扇出后归约成一份清单：

```bash
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`：

```bash
#!/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`：

```bash
#!/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"
```

运行并对比：

```bash
# 串行基准
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` 给出每个任务的成功状态与累计成本，失败的文件可一键重跑：

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

---

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

```mermaid
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"]
```

---

## 九、调参与避坑清单

- **并发度 `-P`**：从 4 起步。只读任务可到 8–16；写任务（worktree 隔离）建议 4–6，避免本地 git I/O 与磁盘抖动。出现 429 就降。
- **模型分级**：批量机械任务用 `--model haiku`，复杂重构用 `sonnet`，省钱又提并发上限。
- **`--bare` 的取舍**：纯无状态只读任务必加；任务需要读项目 CLAUDE.md 或调 skills/MCP 时**不能**加，否则丢上下文。
- **改动落地**：worktree 是临时的。要么在 prompt 里强制 commit 后由归约脚本收集分支再 `cherry-pick`，要么在 WorktreeRemove hook 里改成「不删」并登记路径。
- **`--max-turns` 兜底**：给每个 worker 设上限（如 `--max-turns 20`），防止某个任务陷入死循环吞掉整个并发槽。
- **结果唯一性**：并发写同名文件必撞车，统一用「路径转 slug」做输出文件名。

---

## 延伸阅读

- Claude Code 官方《Run parallel sessions with worktrees》——worktree 并行隔离的权威说明
- Claude Code 官方《Hooks reference》——WorktreeCreate/WorktreeRemove、async hook、五类 handler 的完整 schema
- Claude Code 官方《Headless mode》——`-p`、`--bare`、`--output-format`、`--resume` 全部 flag 参考
- 《Claude Code Batch Processing Complete Guide》——`/batch` 命令与 `claude -p` 大规模变更的工程实践

---

## 训练营适用性评估

- **适合类型**：进阶后端/DevOps/平台工程训练营；AI 工程化、LLM Ops 专题营；CI/CD 自动化实战营。不适合零基础入门营。
- **教学时长**：约 3–4 小时（含动手）。其中 1 小时讲串/并行与 worktree 原理，1.5 小时动手跑通实战案例，1 小时做调参与失败重跑演练。
- **前置衔接**：建议放在学员已掌握「`claude -p` 单任务」「git worktree 基础」之后，作为「从单任务到批处理工程化」的进阶模块。
- **可裁剪**：若课时紧，可只讲 xargs 扇出 + worktree（第一、二、四节），hooks 与结构化归约作为进阶选修。

---

## 参考来源

1. [Run parallel sessions with worktrees — Claude Code Docs](https://code.claude.com/docs/en/worktrees) — 官方 worktree 并行隔离机制与 `--worktree` 标志说明
2. [Hooks reference — Claude Code Docs](https://code.claude.com/docs/en/hooks) — WorktreeCreate/WorktreeRemove 等事件 schema、handler 类型与 async hook 配置
3. [Headless mode — Claude Code Docs](https://code.claude.com/docs/en/headless) — `claude -p`、`--bare`、`--output-format json/stream-json`、`--resume` 等 flag 权威参考
4. [Claude Code Batch Processing Complete Guide](https://smartscope.blog/en/generative-ai/claude/claude-code-batch-processing/) — `/batch` 命令与 `claude -p` 大规模批处理工程实践
5. [Git Worktrees + Claude Code: The 2026 Playbook for Running Parallel Agents](https://developersdigest.tech/blog/git-worktrees-claude-code-parallel-agents-guide) — 多 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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
