Linux Command Line & Bash Scripting Mastery · レッスン

xargs -Pとバックグラウンドジョブによる並列処理

xargsの並列モードと管理されたバックグラウンドジョブのプールを使い、独立したタスクを同時に実行します。

レッスン 2/413 ステップ

「xargs -Pとバックグラウンドジョブによる並列処理」はCoddyKit上の無料Linux Command Line & Bash Scripting Masteryレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLinux Command Line & Bash Scripting Mastery学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。

逐次実行が遅い理由

シェルスクリプトでコマンドを1つずつ実行すると、CPUコアがアイドル状態になります。500個の画像のサイズを変更する場合を考えてみましょう。各 convert の呼び出しは1つのコアしか使わず、残りの7つは待機したままです。

並列化を使うと、複数のタスクを同時に実行できます。Bashには、これを簡単に行うための主要な方法が2つあります。

  • xargs -P — 入力のリストをN個の並列ワーカープロセスに分散します
  • バックグラウンドジョブ(&)+ wait — プロセスを手動で起動し、完了を待ちます

このレッスンでは両方の方法を扱い、それぞれの状況に適した方法を選べるようにします。

xargsの基本を復習

並列化を追加する前に、xargs の動作を確認しましょう。xargs は標準入力から項目を読み取り、コマンドの引数として渡します。

-I {} フラグを使うと、入力項目をコマンド文字列の末尾だけでなく、任意の場所に配置できます。

以下の例では、tr を使ってすべての .txt ファイルを大文字に変換します。各ファイルは一度に1つずつ処理されます(逐次実行の基準)。

#!/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 がワーカープールを自動的に管理するため、スロットが1つ空くと次の項目がすぐに開始されます。

  • -P 0 — 入力の数だけプロセスを起動します(大きなリストでは慎重に使用してください)
  • -P 4 — 常に最大4個のワーカーを実行します
  • -n 1 — 起動したプロセスごとに入力項目を必ず1つ渡します(よく併用されるフラグです)

-n 1 -P 4 の組み合わせは最も一般的なパターンです。1ワーカーにつき1項目を割り当て、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 を指定すると、最大8個の圧縮を同時に実行し、すべてのCPUコアを使い切れます。

-n 1 によって、各並列ワーカーがファイル名を必ず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 より細かい制御が必要な場合があります。そのときは、シェル組み込みのバックグラウンド演算子 &を使って、ジョブを手動で起動します。

任意のコマンドの末尾に & を付けると、すぐにスクリプトへ制御が戻ります。子プロセスはバックグラウンドで実行され、親プロセスは処理を続けます。最後に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以降)を呼び出し、いずれか1つのジョブが終了するまで待ってから次を起動します

このパターンは、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を同時に取得し、それぞれを一意な名前のファイルに保存します。

使用している主なフラグは次のとおりです。

  • -P 8 — curlプロセスを8個同時に実行します
  • -n 1 — 1回のcurl呼び出しにつきURLを1つ渡します
  • --create-dirs -o — curlが生成したファイル名で保存します
#!/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、シェル関数を組み合わせる

複数行のシェル関数を xargs で使うには、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 で囲み、逐次実行の基準と比較します。実際の高速化の度合いは、次の要因によって決まります。

  • タスクの独立性 — ロックなしに書き込み可能な状態を共有してはいけません
  • オーバーヘッド — 小さなタスクでは、プロセス起動コスト(1回あたり約5~20ミリ秒)が重要になります
  • リソース競合 — CPUより先にディスクI/Oが飽和することがあります

以下に簡単なベンチマークのパターンを示します。まず逐次実行し、次に並列実行して、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で並列実行を行うための、信頼できる2つの方法を学びました。

  • xargs -n 1 -P N — 最も簡単な方法です。xargsがワーカープールを自動的に管理します。入力が単純なリストで、各項目を1つのコマンドに対応付ける場合に適しています
  • バックグラウンドジョブ(&)+ wait — スクリプトを完全に制御できます。ジョブごとのPID、動的な入力、細かな終了コード処理が必要な場合に不可欠です。並行数を制限するには、カウンターと wait -n を使います

今後も覚えておくべき重要なルール:

  • xargs に渡す前に、export -f でシェル関数をエクスポートします
  • スペースを含むファイル名を安全に扱うには、-print0 / -0 を使います
  • PIDを配列に保存し、失敗を検出するために wait "$pid" を個別に呼び出します
  • time でベンチマークを行います。並列化の効果が出るのは、タスクのオーバーヘッドがプロセス起動コストを上回る場合だけです
  • CPUバウンドの処理には -P $(nproc) を設定し、I/Oバウンドの処理にはより大きな倍率を設定します
無料で開始

AI チューターと学ぶ Bash — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
22
レッスン
88

よくある質問

「xargs -Pとバックグラウンドジョブによる並列処理」レッスンは無料ですか?

はい。「xargs -Pとバックグラウンドジョブによる並列処理」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応の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を演習し、24時間対応の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. スクリプトのプロファイリングと不要なサブシェルの回避
  2. xargs -Pとバックグラウンドジョブによる並列処理
  3. GNU parallelによるワークロードのオーケストレーション
  4. スループット向上のためのストリーミングパイプラインと名前付きパイプ
← Linux Command Line & Bash Scripting Masteryに戻る