しばしばプロセスを並列実行させた方が早くなる作業があります。それはある作業を実行するのにまた別の作業が前提となっていない作業です。例えば計算処理とファイルのコピーの二つの作業です。それぞれの作業が他の作業に関わっていないのであれば、それらは並列化すると概ね早くなります。それぞれの作業が独立していても早くならない場合があります。それはマシンの計算資源がボトルネックとなっている場合です。例えばファイルのコピーはどれだけ並列化してもマシンの読み書きの速度より処理が速くなることはありません。また並列化をする際の懸念点としてこの計算資源を食い尽くしかねない点があります。大量のメモリを要する処理が複数同時に実行されると、一つずつ処理していた時は問題なかったメモリ枯渇の問題が現れたりします。こうなるとエラーで止まったり、スワップ領域をガンガン使いだしてメモリ上で済んでいた一つずつの処理よりも作業に時間がかかったりしてしまいます。この記事ではそういった懸念点も考慮できる簡易なシェルスクリプトによる並列処理コードの例を紹介します。
実際のコードと実行例が次です。
#!/bin/bash # 同時実行数の上限を設定 MAX_JOBS=5 # 並列実行したいコマンドを列挙。ここではランダムな待ち時間のsleepコマンドを実行する declare -a jobs=( "sleep $((RANDOM % 10 + 1))" "sleep $((RANDOM % 10 + 1))" "sleep $((RANDOM % 10 + 1))" "sleep $((RANDOM % 10 + 1))" "sleep $((RANDOM % 10 + 1))" "sleep $((RANDOM % 10 + 1))" "sleep $((RANDOM % 10 + 1))" ) echo "並列実行を開始します。" # 配列の各要素に対してループ for job in "${jobs[@]}" do # バックグラウンドでジョブを開始 echo "ジョブ開始: $job" eval $job & # 実行中のジョブ数を取得し、MAX_JOBSに達していれば待機 while [ $(jobs -p | wc -l) -ge $MAX_JOBS ] do sleep 1 done done # すべてのジョブが終了するのを待機 wait echo "並列実行が完了しました。"
やっていることはコメントの通りです。指定したジョブをバックグラウンドで実行させ、同時に実行されるジョブの数が最大値に達した場合、新たなジョブの開始を一時停止させるといった具合です。これにより計算資源にやさしくしつつ、作業の効率化を図ることができます。
計算資源にやさしいと言っても単に多くなりすぎないという程度の乱雑なスクリプトです。よほどの環境でなければ何がしかのより多機能な言語が使えますので、実行されているプロセスや各計算資源の状況を見て動的に処理を決定するなどといった細かい制御が必要な場合はそちらを使った方がいいです。