CPU使用率を最大化するため(EC2のDebian Lennyで物事を実行しています)、並列にジョブを起動する簡単なスクリプトがあります。
#!/bin/bash
for i in Apache-200901*.log; do echo "Processing $i ..."; do_something_important; done &
for i in Apache-200902*.log; do echo "Processing $i ..."; do_something_important; done &
for i in Apache-200903*.log; do echo "Processing $i ..."; do_something_important; done &
for i in Apache-200904*.log; do echo "Processing $i ..."; do_something_important; done &
...
私はこの作業ソリューションに非常に満足していますが、すべてのループが完了した後にのみ実行されるコードをさらに記述する方法を理解できませんでした。
これを制御する方法はありますか?
そのためのbash
組み込みコマンドがあります。
wait [n ...]
Wait for each specified process and return its termination sta‐
tus. Each n may be a process ID or a job specification; if a
job spec is given, all processes in that job’s pipeline are
waited for. If n is not given, all currently active child pro‐
cesses are waited for, and the return status is zero. If n
specifies a non-existent process or job, the return status is
127. Otherwise, the return status is the exit status of the
last process or job waited for.
GNU Parallelを使用すると、スクリプトがさらに短くなり、おそらくより効率的になります。
parallel 'echo "Processing "{}" ..."; do_something_important {}' ::: Apache-*.log
これにより、CPUコアごとに1つのジョブが実行され、すべてのファイルが処理されるまで実行されます。
ソリューションは基本的に、実行前にジョブをグループに分割します。ここでは、4つのグループに32のジョブがあります。
代わりに、GNU Parallelは、プロセスが終了すると新しいプロセスを生成します-CPUをアクティブに保ち、時間を節約します。
詳しく知ることができ:
私は最近これを行わなければならず、次の解決策に終わりました:
while true; do
wait -n || {
code="$?"
([[ $code = "127" ]] && exit 0 || exit "$code")
break
}
done;
仕組みは次のとおりです。
wait -n
(潜在的に多数の)バックグラウンドジョブの1つが終了するとすぐに終了します。常にtrueと評価され、ループは次の状態になるまで続きます。
127
:最後のバックグラウンドジョブが正常に終了しました。その場合、終了コードを無視し、コード0でサブシェルを終了します。set -e
、これにより、スクリプトが早期に終了し、失敗したバックグラウンドジョブの終了コードを通過することが保証されます。
これは私の粗解です:
function run_task {
cmd=$1
output=$2
concurency=$3
if [ -f ${output}.done ]; then
# experiment already run
echo "Command already run: $cmd. Found output $output"
return
fi
count=`jobs -p | wc -l`
echo "New active task #$count: $cmd > $output"
$cmd > $output && touch $output.done &
stop=$(($count >= $concurency))
while [ $stop -eq 1 ]; do
echo "Waiting for $count worker threads..."
sleep 1
count=`jobs -p | wc -l`
stop=$(($count > $concurency))
done
}
アイデアは、「ジョブ」を使用してバックグラウンドでアクティブな子の数を確認し、この数が下がるまで待つ(子が終了する)ことです。子が存在すると、次のタスクを開始できます。
ご覧のとおり、同じ実験/コマンドを複数回実行することを避けるために、少し余分なロジックもあります。それは私のために仕事をします。しかし、このロジックはスキップするか、さらに改善することができます(たとえば、ファイル作成のタイムスタンプ、入力パラメーターなどを確認します)。