使用 GNU parallel 编排工作负载
使用 GNU parallel、任务槽位和结果排序,将大型输入集分配到多个 CPU 核心。
使用 GNU parallel 编排工作负载 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
GNU parallel 是什么,为什么使用它
GNU parallel 是一种 Shell 工具,可以让您在一台或多台机器上并行运行作业。使用 parallel 后,不必在 for 循环中逐个处理大型项目列表,而是可以将工作同时分配到所有可用的 CPU 核心上。
- 速度:顺序执行需要 8 分钟的任务,在 8 核机器上约 1 分钟即可完成。
- 简单:它可以从标准输入、文件或参数列表中接收输入,无需手动管理进程。
- 安全:不同作业的输出彼此分开,结果绝不会交错。
请使用 sudo apt install parallel(Debian/Ubuntu)或 brew install parallel(macOS)进行安装。使用 parallel --version 验证安装结果。
您的第一个 parallel 命令
parallel 最简单的形式是从标准输入读取项目,并为每个项目运行一次命令。占位符 {} 表示当前输入项目。
下面的示例使用 gzip 并发压缩五个日志文件。不使用 parallel 时,每个文件都要依次压缩。使用它后,最多可同时压缩 N 个文件(其中 N = CPU 核心数)。
#!/usr/bin/env bash
# Create sample files first
for i in 1 2 3 4 5; do
dd if=/dev/urandom bs=1M count=2 of="log_${i}.txt" 2>/dev/null
done
# Compress all of them in parallel
ls log_*.txt | parallel gzip {}
echo "Done. Compressed files:"
ls log_*.txt.gz使用 -j 控制作业槽位
默认情况下,parallel 为每个 CPU 核心运行一个作业。您可以使用 -j(或 --jobs)标志覆盖此设置。
-j 4——同时运行恰好 4 个作业-j 0——输入有多少个项目就运行多少个作业(请谨慎使用!)-j 200%——运行数量为 CPU 核心数两倍的作业(适用于 I/O 密集型工作)-j 50%——只使用一半的可用核心
对于 CPU 密集型任务,-j $(nproc) 通常是最佳选择。对于网络或磁盘 I/O 任务,可以安全地超过核心数,因为作业大部分时间都在等待。
#!/usr/bin/env bash
# Show how many cores are available
echo "CPU cores: $(nproc)"
# Run 8 sleep jobs but limit to 3 at a time
# -j 3 means at most 3 jobs run simultaneously
seq 1 8 | parallel -j 3 'echo "Starting job {}"; sleep 1; echo "Done job {}"'
echo "All jobs finished."从文件和参数读取输入
parallel 对输入列表的来源非常灵活,不局限于通过管道从标准输入读取。
- 从文件读取:
parallel -a urls.txt wget {} - 内联参数列表:
parallel echo ::: apple banana cherry - 多个参数来源(笛卡尔积):
parallel echo {1}-{2} ::: a b c ::: 1 2——生成 a-1, a-2, b-1, b-2, c-1, c-2 - 明确从标准输入读取:
cat list.txt | parallel -j4 process {}
分隔符 ::: 告诉 parallel 将后面的值用作参数来源,而不是从标准输入读取。
#!/usr/bin/env bash
# Inline list with :::
parallel echo 'Hello from {}' ::: Alice Bob Carol Dave
echo '---'
# Cartesian product: combine two lists
# Generates: dev-v1, dev-v2, prod-v1, prod-v2
parallel echo 'Deploy {1} to env {2}' ::: v1 v2 ::: dev prod占位符:操作输入标记
parallel 提供了多种占位符替换方式,可以自动提取输入字符串的各个部分——处理文件路径时非常有用。
{}——完整的输入项目{.}——去掉文件扩展名的输入(report.csv → report){/}——仅保留基本文件名(去掉目录路径){//}——仅保留目录路径{/.}——去掉扩展名的基本文件名
这些占位符消除了在作业命令中调用 basename / dirname 的需要,使管道更简洁、更快速。
#!/usr/bin/env bash
# Demonstrate placeholder substitutions
parallel --dry-run 'convert {} -resize 800x600 {.}_thumb.jpg' \
::: /photos/vacation/beach.png /photos/work/team.png
# {.} strips extension: /photos/vacation/beach
# Result command shown (--dry-run does NOT execute):
# convert /photos/vacation/beach.png -resize 800x600 /photos/vacation/beach_thumb.jpg
# convert /photos/work/team.png -resize 800x600 /photos/work/team_thumb.jpg
echo 'No files were changed (dry run)'使用 --keep-order 保持输出有序
当作业完成时间不同时,其标准输出会按照完成顺序显示。这可能导致日志难以阅读,也会使下游解析变得不可靠。
以下两个标志用于控制输出顺序:
--keep-order(-k)——按照输入顺序输出每个作业的结果,即使后面的作业先完成也一样。输出会被缓冲,直到前面的作业完成。--line-buffer——一种折中方案:行一旦到达就输出完整的行,无需等待作业完成,但绝不会交错输出未写完的行。
当下游使用者要求结果保持输入顺序时(例如生成排序后的报告),请使用 -k。如果顺序无关紧要并且希望尽快看到结果,则不要使用它。
#!/usr/bin/env bash
# Without -k: output order is unpredictable
echo '--- Without --keep-order ---'
seq 5 1 1 | parallel 'sleep 0.$((RANDOM % 5)); echo "Result for {}"'
echo
# With -k: output always appears as 5, 4, 3, 2, 1
echo '--- With --keep-order (-k) ---'
seq 5 1 1 | parallel -k 'sleep 0.$((RANDOM % 5)); echo "Result for {}"'对输出进行分组以避免交错
即使输出有序,如果一个任务打印多行内容,这些行也可能与另一个同时运行的任务所打印的行交错在一起。parallel 会自动解决这个问题:它会缓冲每个任务的完整标准输出和标准错误,等任务结束后,再将它们作为一个不可分割的整体打印出来。
此行为默认启用。如果您需要实时流式输出(例如带有进度条的长时间运行任务),可以使用 --ungroup 将其禁用,但这样输出就可能再次交错。
- 默认:按任务对输出进行分组,便于安全解析。
--ungroup:实时输出流,适合交互式监控。--line-buffer:折中方案,不会拆分行,但不同任务可能在行边界处交错。
在 Shell 函数中传递参数
有时,您希望并行执行的工作不只是一个命令,而是一个包含多个步骤的 Shell 函数。您可以结合使用 export -f 和 env_parallel 将函数传递给 parallel,也可以直接调用 bash -c。
对于复杂任务,最安全且具有良好可移植性的方式是使用 bash -c '...' 模式。当命令以 _ {} 结尾时,占位符 {} 会作为 $1 传入。
#!/usr/bin/env bash
# Define a multi-step processing function
process_item() {
local item="$1"
echo "[START] $item"
# Simulate two steps
sleep 0.2
local result=$(echo "$item" | tr '[:lower:]' '[:upper:]')
echo "[END] $item -> $result"
}
export -f process_item
# Run the function in parallel for each input
echo 'alpha beta gamma delta epsilon' | tr ' ' '\n' \
| parallel -j 3 process_item {}使用 --delay 和 --retries 进行限流与重试
并行访问外部服务(API、远程服务器、数据库)时,您通常需要进行速率限制并提高容错能力。
--delay N:每启动一个新任务前等待 N 秒(允许使用0.5等小数值)。可防止服务被请求洪流压垮。--retries N:如果任务以非零状态退出,则最多重试 N 次后再放弃。每次重试都会计为一个新的任务槽位。--timeout N:如果任务运行时间超过 N 秒,则终止该任务。与--retries结合使用,可以妥善处理无响应的任务。
示例:下载 50 个 URL,最多保持 4 个并发连接,启动任务之间间隔 0.5 秒,并在失败时重试 3 次。
#!/usr/bin/env bash
# Simulate downloading URLs with throttling and retries
# (using echo instead of curl so this is self-contained)
download_url() {
local url="$1"
# Randomly fail ~30% of the time to demo --retries
if (( RANDOM % 10 < 3 )); then
echo "FAIL: $url" >&2
return 1
fi
echo "OK: $url downloaded"
}
export -f download_url
printf 'https://example.com/file%d\n' $(seq 1 10) \
| parallel -j 4 --delay 0.2 --retries 3 download_url {}
echo 'All downloads attempted.'使用 --sshloginfile 将工作分配到远程主机
parallel 可以通过 SSH 将任务透明地分配到远程机器上,让您无需特殊的集群软件,就能将其作为轻量级集群计算工具使用。
--sshlogin user@host:在指定的远程主机上运行任务。--sshloginfile machines.txt:从文件中读取主机列表(每行一个)。使用:作为特殊条目,还可以同时使用本地机器。--transfer:在处理前将输入文件复制到远程主机。--return {}:任务结束后将结果文件复制回来。--cleanup:取回文件后删除远程主机上的传输文件。
远程主机必须安装 parallel,并配置基于 SSH 密钥的身份验证(不提示输入密码)。
进度报告与日志记录
对于长时间运行的工作负载,监控进度并在事后诊断故障至关重要。
--progress:实时打印摘要行,显示正在运行、已完成和剩余的任务数量。--eta:根据截至目前的平均任务耗时,估算完成所需的时间。--joblog results.log:写入一个以制表符分隔的日志文件,每个已完成任务占一行,其中包括退出代码、运行时长和执行的命令。对于审计故障非常有价值。--resume --joblog results.log:跳过日志文件中已经出现且退出代码为 0 的任务。如果批处理运行被中断,您可以从中断处继续,而不必重新执行已成功的工作。
--joblog 与 --resume 的组合,是 GNU parallel 用于构建可靠生产流水线的最强大功能之一。
#!/usr/bin/env bash
LOGFILE="/tmp/parallel_demo_$$.log"
# Run jobs and record results to a log
seq 1 12 | parallel \
--jobs 4 \
--progress \
--joblog "$LOGFILE" \
'sleep 0.1; echo "Processed item {}"'
echo
echo '=== Job Log (first 5 entries) ==='
head -6 "$LOGFILE"
# Show only failed jobs (exit value != 0)
echo '=== Failed jobs ==='
awk 'NR>1 && $7 != 0 { print $0 }' "$LOGFILE" || echo '(none)'
rm -f "$LOGFILE"知识检查:任务槽位标志
测试您对 parallel 如何控制并发的理解。
课程回顾:使用 GNU parallel 编排工作负载
您已经学习了使用 GNU parallel 将大型输入集分配到 CPU 核心上的核心工具。以下是需要掌握的要点:
- 基本用法:将列表通过管道传入
parallel command {},其中{}会被每个输入项替换。 - 任务槽位(
-j):精确控制并发数:CPU 密集型工作使用核心数,I/O 密集型工作则使用更高的百分比。 - 占位符:(
{.}、{/}、{//}、{/.})可以直接提取路径组成部分,无需额外命令。 - 输出控制:
-k保持输入顺序;默认分组可防止行交错;--ungroup提供实时流式输出。 - 可靠性:
--retries、--timeout和--delay可使并行流水线更能抵御不稳定的任务和速率限制。 - 可审计性:
--joblog记录每个任务的结果;--resume让您在运行中断后从上次停止的位置继续。 - 横向扩展:
--sshloginfile通过 SSH 将任务分配到远程机器,无需承担集群管理开销。
熟练掌握这些选项后,parallel 就会变成一个直接集成在 Shell 中、达到生产级别的工作负载编排工具。
常见问题解答
「使用 GNU parallel 编排工作负载」课时是免费的吗?
是的 — 「使用 GNU parallel 编排工作负载」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。
「使用 GNU parallel 编排工作负载」这节课中我会学到什么?
使用 GNU parallel、任务槽位和结果排序,将大型输入集分配到多个 CPU 核心。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「使用 GNU parallel 编排工作负载」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?
能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 分析脚本性能并避免无用的子 Shell
- 使用 xargs -P 与后台任务实现并行
- 使用 GNU parallel 编排工作负载
- 流式管道与命名管道吞吐