0Pricing
Linux Command Line & Bash Scripting Mastery · 课时

使用 xargs -P 与后台任务实现并行

使用 xargs 并行模式和受管理的后台任务池并发运行相互独立的任务。

使用 xargs -P 与后台任务实现并行 是 CoddyKit 上的免费 Linux Command Line & Bash Scripting Mastery 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Linux Command Line & Bash Scripting Mastery 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

为什么顺序执行速度慢

在 Shell 脚本中依次运行命令时,CPU 核心会处于空闲状态。以调整 500 张图像的大小为例:每次 convert 调用只使用一个核心,而另外七个核心都在闲置。

并行处理可以通过同时分派多个任务来解决这个问题。Bash 中有两个主要工具可以轻松实现这一点:

  • xargs -P——将输入列表分发给 N 个并行工作进程
  • 后台作业(&)+ wait——手动创建进程并收集其结果

本课将介绍这两种方法,帮助您根据不同情况选择合适的工具。

xargs 基础回顾

在添加并行处理之前,先回顾一下 xargs 的工作方式。它从标准输入读取项目,并将其作为参数传递给命令。

-I {} 标志允许您将输入项目放在命令字符串中的任意位置,而不只是末尾。

下面的示例使用 tr 将每个 .txt 文件转换为大写。每个文件一次处理一个,这是顺序执行的基准。

#!/usr/bin/env bash
# Create sample files
mkdir -p /tmp/xargs_demo
for i in 1 2 3; do
  echo "hello world $i" > /tmp/xargs_demo/file$i.txt
done

# Process files one at a time (sequential)
find /tmp/xargs_demo -name '*.txt' | xargs -I {} sh -c 'tr a-z A-Z < "$1"' _ {}

# Cleanup
rm -rf /tmp/xargs_demo

介绍 xargs -P

为 xargs 添加 -P N 标志,即可并行运行最多 N 个进程。xargs 会自动管理工作进程池——当一个槽位空闲时,下一个项目会立即开始处理。

  • -P 0——输入有多少个项目就创建多少个进程(处理大型列表时请谨慎使用)
  • -P 4——任何时刻最多保持 4 个工作进程运行
  • -n 1——每个被调用的进程恰好接收一个输入项目(常用的搭配标志)

-n 1 -P 4 组合是最常见的模式:每个工作进程处理一个项目,同时运行四个工作进程。

#!/usr/bin/env bash
# Simulate 8 tasks, each taking ~1 second
# Sequential would take ~8s; parallel with -P 4 takes ~2s

process_item() {
  local item="$1"
  sleep 1
  echo "Done: $item"
}
export -f process_item

time printf '%s\n' task{1..8} | xargs -n 1 -P 4 bash -c 'process_item "$@"' _

并行处理文件

一个实用场景是同时压缩许多日志文件。不使用 -P 时,每次 gzip 调用都会阻塞下一次调用。使用 -P 8 后,最多可以同时运行八个压缩操作,从而充分利用所有 CPU 核心。

请注意,-n 1 可确保每个并行工作进程恰好接收一个文件名——当文件名可能包含空格时,这一点至关重要(为确保安全,请结合使用 -d '\n' 或 -print0 / -0)。

#!/usr/bin/env bash
# Create dummy log files
mkdir -p /tmp/logs_demo
for i in $(seq 1 12); do
  dd if=/dev/urandom bs=1K count=64 2>/dev/null > /tmp/logs_demo/app_$i.log
done

echo "Files before: $(ls /tmp/logs_demo | wc -l)"

# Compress all .log files in parallel (up to 8 workers)
find /tmp/logs_demo -name '*.log' -print0 \
  | xargs -0 -n 1 -P 8 gzip --fast

echo "Files after : $(ls /tmp/logs_demo | wc -l)"
rm -rf /tmp/logs_demo

选择合适的 -P 值

将 -P 设置得过低会浪费核心;设置得过高则会导致系统频繁换入换出。一个较好的起始值是逻辑 CPU 核心数:

  • CPU 密集型任务(压缩、编码):-P $(nproc)
  • I/O 密集型任务(网络调用、磁盘读取):-P $(($(nproc) * 4)) 或更高,因为工作进程大部分时间都在等待
  • 受内存限制的任务:计算 available_RAM / task_RAM_usage,并将并发数限制在该值

nproc 会返回可用处理单元的数量,因此可以便携地替代硬编码的数字。

#!/usr/bin/env bash
CORES=$(nproc)
IO_WORKERS=$(( CORES * 4 ))

echo "CPU cores   : $CORES"
echo "CPU-bound -P: $CORES"
echo "I/O-bound -P: $IO_WORKERS"

# Example: parallel curl downloads (I/O-bound)
# printf '%s\n' url1 url2 ... | xargs -n 1 -P "$IO_WORKERS" curl -sSO

使用 & 的后台作业

有时您需要的控制力超出 xargs 所能提供的范围,例如为每个作业处理错误、使用动态列表或处理复杂的参数形式。这时可以使用 Shell 内置的后台运算符 & 手动创建作业。

在任意命令后附加 &,脚本就会立即恢复控制权。子进程在后台运行,而父进程继续执行。最后调用 wait,阻塞等待所有子进程完成。

#!/usr/bin/env bash
process() {
  local id="$1"
  sleep $(( RANDOM % 3 + 1 ))
  echo "Job $id finished at $(date +%T)"
}

echo "Launching 5 background jobs..."
for id in $(seq 1 5); do
  process "$id" &
done

wait   # Block until every background job completes
echo "All jobs done."

使用作业池限制并发数

使用 & 一次创建所有作业,在列表较大时可能耗尽内存。作业池会确保任何时刻运行的作业不超过 N 个:

  • 每创建一个作业后,使用 jobs -r | wc -l 检查当前处于活动状态的后台作业数量
  • 如果数量达到上限,则调用 wait -n(Bash 4.3+),等待任意一个作业完成后再创建下一个

这种模式模拟了 xargs -P 的内部工作方式,同时让您能够围绕每个作业灵活编写脚本。

#!/usr/bin/env bash
MAX_JOBS=3

process() {
  local id="$1"
  sleep $(( RANDOM % 3 + 1 ))
  echo "Task $id done"
}

for id in $(seq 1 10); do
  # Throttle: wait for a slot if pool is full
  while (( $(jobs -r | wc -l) >= MAX_JOBS )); do
    wait -n 2>/dev/null || true
  done
  process "$id" &
done

wait
echo "All 10 tasks complete."

获取并行作业的退出代码

后台作业有一个必须注意的问题:如果子进程失败,父脚本不会自动得知。您必须捕获每个子进程的 PID,并使用 wait <pid> 检查其退出状态。

下面的模式会将每个 PID 存储在数组中,然后遍历该数组并调用 wait "$pid",该调用会返回指定子进程的退出代码。

#!/usr/bin/env bash
set -euo pipefail

task() {
  local id="$1"
  sleep 1
  if (( id == 3 )); then
    echo "Task $id: FAILED" >&2
    return 1
  fi
  echo "Task $id: ok"
}

pids=()
for id in $(seq 1 5); do
  task "$id" &
  pids+=("$!")
done

failed=0
for pid in "${pids[@]}"; do
  if ! wait "$pid"; then
    echo "PID $pid exited with error" >&2
    (( failed++ ))
  fi
done

(( failed == 0 )) && echo "All OK" || { echo "$failed job(s) failed"; exit 1; }

使用 xargs -P 并行下载

网络 I/O 是高度并行处理的典型场景——每个工作进程大部分时间都在等待数据传输。下面的示例会并发获取多个 URL,并将每个 URL 保存到名称唯一的文件中。

使用的关键标志:

  • -P 8——同时运行八个 curl 进程
  • -n 1——每次 curl 调用处理一个 URL
  • --create-dirs -o——curl 将内容保存到根据 URL 派生的文件名
#!/usr/bin/env bash
# Download several small public files in parallel
URLs=(
  "https://httpbin.org/bytes/1024"
  "https://httpbin.org/bytes/2048"
  "https://httpbin.org/bytes/512"
  "https://httpbin.org/bytes/4096"
)

mkdir -p /tmp/parallel_dl

printf '%s\n' "${URLs[@]}" | xargs -n 1 -P 4 bash -c '
  url="$1"
  out="/tmp/parallel_dl/$(echo "$url" | md5sum | cut -c1-8).bin"
  curl -sSf "$url" -o "$out" && echo "Saved $out"
' _

ls -lh /tmp/parallel_dl/
rm -rf /tmp/parallel_dl

组合使用 find、xargs -P 和 Shell 函数

要让 xargs 使用多行 Shell 函数,您必须先通过 export -f function_name 将其导出,然后在 xargs 中通过 bash -c 'function_name "$@"' _ 调用它。

这种模式可以让每个并行工作进程拥有完整的脚本编写能力:日志记录、错误处理和条件逻辑都可以针对每个项目单独执行。

#!/usr/bin/env bash
mkdir -p /tmp/proc_demo
for i in $(seq 1 8); do echo "data $i" > /tmp/proc_demo/item_$i.txt; done

process_file() {
  local f="$1"
  local base
  base=$(basename "$f" .txt)
  # Simulate work: count words and append a timestamp
  local wc
  wc=$(wc -w < "$f")
  echo "[$base] words=$wc processed=$(date +%T)" >> "/tmp/proc_demo/${base}.result"
}
export -f process_file

find /tmp/proc_demo -name '*.txt' -print0 \
  | xargs -0 -n 1 -P "$(nproc)" bash -c 'process_file "$@"' _

grep '' /tmp/proc_demo/*.result
rm -rf /tmp/proc_demo

使用 time 测量加速效果

在宣称性能有所提升之前,务必先进行测量。使用 time 包装并行命令,并与顺序执行的基准进行比较。实际加速效果取决于:

  • 任务独立性——如果没有锁,任务就不能共享可写状态
  • 开销——对于细小任务,创建进程的成本(每个约 5–20 毫秒)很重要
  • 资源争用——磁盘 I/O 甚至可能早于 CPU 达到饱和

下面展示了一个简单的基准测试模式:先顺序运行,再并行运行,然后比较 real 的实际耗时。

#!/usr/bin/env bash
work() { sleep 0.2; }   # simulate a 200ms task
export -f work

ITEMS=$(seq 1 16)

echo "=== Sequential ==="
time printf '%s\n' $ITEMS | xargs -n 1 bash -c 'work' _

echo
echo "=== Parallel ($(nproc) workers) ==="
time printf '%s\n' $ITEMS | xargs -n 1 -P "$(nproc)" bash -c 'work' _

知识检查:xargs -P 的行为

测试您对使用 xargs -P 进行并行执行的理解。

课程回顾

现在,您已经掌握了 Bash 中实现并行执行的两种可靠技术:

  • xargs -n 1 -P N——最简单的方法;xargs 会自动管理工作进程池。最适合输入为普通列表且每个项目对应一个命令的情况。
  • 后台作业(&)+ wait——提供完整的脚本控制;当您需要获取每个作业的 PID、处理动态输入或精细处理退出代码时,这种方法必不可少。请结合计数器使用 wait -n 来限制并发数。

请牢记以下规则:

  • 将 Shell 函数传递给 xargs 之前,使用 export -f 导出它们
  • 使用 -print0 / -0 安全处理包含空格的文件名
  • 将 PID 捕获到数组中,并分别调用 wait "$pid" 来检测失败
  • 使用 time 进行基准测试——只有当任务开销超过创建进程的成本时,并行处理才会带来收益
  • 对于 CPU 密集型任务,设置为 -P $(nproc);对于 I/O 密集型工作负载,设置为更高的倍数

常见问题解答

「使用 xargs -P 与后台任务实现并行」课时是免费的吗?

是的 — 「使用 xargs -P 与后台任务实现并行」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Linux Command Line & Bash Scripting Mastery 课程的其余内容,请升级到 CoddyKit PRO。 Linux Command Line & Bash Scripting Mastery 课程共包含 4 节课。

「使用 xargs -P 与后台任务实现并行」这节课中我会学到什么?

使用 xargs 并行模式和受管理的后台任务池并发运行相互独立的任务。 你通过在浏览器中直接运行的动手代码来练习 Linux Command Line & Bash Scripting Mastery,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Linux Command Line & Bash Scripting Mastery 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Linux Command Line & Bash Scripting Mastery 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「使用 xargs -P 与后台任务实现并行」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Linux Command Line & Bash Scripting Mastery 课中编写并运行代码吗?

能。每节 Linux Command Line & Bash Scripting Mastery 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 分析脚本性能并避免无用的子 Shell
  2. 使用 xargs -P 与后台任务实现并行
  3. 使用 GNU parallel 编排工作负载
  4. 流式管道与命名管道吞吐
← 返回 Linux Command Line & Bash Scripting Mastery