xargs -Pとバックグラウンドジョブによる並列処理
xargsの並列モードと管理されたバックグラウンドジョブのプールを使い、独立したタスクを同時に実行します。
「xargs -Pとバックグラウンドジョブによる並列処理」はCoddyKit上の無料DevOps Bootcampレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはDevOps Bootcamp学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 DevOps Bootcampコースには全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_demoxargs -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_dlfind、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_demotimeで高速化を測定する
改善できたと判断する前に、必ず測定してください。並列コマンドを 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 チューターと学ぶ DevOps Bootcamp — 無料
ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。
- コース
- 142
- レッスン
- 568
よくある質問
「xargs -Pとバックグラウンドジョブによる並列処理」レッスンは無料ですか?
はい。「xargs -Pとバックグラウンドジョブによる並列処理」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、DevOps Bootcampコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 DevOps Bootcampコースには全4レッスンが含まれています。
「xargs -Pとバックグラウンドジョブによる並列処理」で何を学びますか?
xargsの並列モードと管理されたバックグラウンドジョブのプールを使い、独立したタスクを同時に実行します。 ブラウザで直接実行するハンズオンコードでDevOps Bootcampを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
DevOps Bootcampを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのDevOps Bootcampは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「xargs -Pとバックグラウンドジョブによる並列処理」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このDevOps Bootcampレッスンでコードを書いて実行できますか?
はい。すべてのDevOps Bootcampレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- スクリプトのプロファイリングと不要なサブシェルの回避
- xargs -Pとバックグラウンドジョブによる並列処理
- GNU parallelによるワークロードのオーケストレーション
- スループット向上のためのストリーミングパイプラインと名前付きパイプ