Bashで同時実行ジョブの数を制限する簡単な方法はありますか?つまり、バックグラウンドで実行されているn個を超える同時実行ジョブがある場合に&ブロックを作成することを意味します。
私はこれをpsで実装できることを知っています| grepスタイルのトリックですが、もっと簡単な方法はありますか?
GNU Parallel http://www.gnu.org/software/parallel/ がインストールされている場合、これを行うことができます:
parallel gzip ::: *.log
すべてのログファイルがgzipされるまで、CPUコアごとに1つのgzipを実行します。
大きなループの一部である場合は、代わりにsem
を使用できます。
for i in *.log ; do
echo $i Do more stuff here
sem -j+0 gzip $i ";" echo done
done
sem --wait
同じことを行いますが、各ファイルに対してより多くのことを行う機会を与えます。
GNU Parallelがディストリビューション用にパッケージ化されていない場合、次のようにインストールすることができますGNU Parallel
$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 3374ec53bacb199b245af2dda86df6c9
12345678 3374ec53 bacb199b 245af2dd a86df6c9
$ md5sum install.sh | grep 029a9ac06e8b5bc6052eac57b2c3c9ca
029a9ac0 6e8b5bc6 052eac57 b2c3c9ca
$ sha512sum install.sh | grep f517006d9897747bed8a4694b1acba1b
40f53af6 9e20dae5 713ba06c f517006d 9897747b ed8a4694 b1acba1b 1464beb4
60055629 3f2356f3 3e9c4e3c 76e3f3af a9db4b32 bd33322b 975696fc e6b23cfb
$ bash install.sh
グローバルにインストールできない場合は、ダウンロードして署名を確認し、個人用インストールを実行します。
GNU Parallelの紹介動画をご覧ください:詳細は https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
小さなbashスクリプトはあなたを助けることができます:
# content of script exec-async.sh
joblist=($(jobs -p))
while (( ${#joblist[*]} >= 3 ))
do
sleep 1
joblist=($(jobs -p))
done
$* &
あなたが呼び出す場合:
. exec-async.sh sleep 10
... 4回、最初の3つの呼び出しはすぐに戻り、4番目の呼び出しは実行中のジョブが3つ未満になるまでブロックされます。
jobs
は現在のセッションのジョブのみをリストするため、.
を前に付けて、現在のセッション内でこのスクリプトを開始する必要があります。
内部のsleep
は醜いですが、最初のジョブが終了するのを待つ方法が見つかりませんでした。
次のスクリプトは、関数でこれを行う方法を示しています。 bgxupdate
関数とbgxlimit
関数をスクリプトに含めるか、次のようにスクリプトから取得される別のファイルにそれらを含めることができます。
. /path/to/bgx.sh
これには、プロセスの複数のグループを独立して維持できるという利点があります(たとえば、1つのグループを制限が10で、別のグループを制限が3で完全に別個のグループで実行できます)。
bash
ビルトインjobs
を使用してサブプロセスのリストを取得しましたが、サブプロセスを個別の変数に保持しています。下のループでは、bgxlimit
関数を呼び出す方法を確認できます。
bgxgrp
に転送します。bgxlimit
を呼び出します。もちろん、グループが1つしかない場合は、転送するのではなく、直接bgxgrp
を使用してください。
#!/bin/bash
# bgxupdate - update active processes in a group.
# Works by transferring each process to new group
# if it is still active.
# in: bgxgrp - current group of processes.
# out: bgxgrp - new group of processes.
# out: bgxcount - number of processes in new group.
bgxupdate() {
bgxoldgrp=${bgxgrp}
bgxgrp=""
((bgxcount = 0))
bgxjobs=" $(jobs -pr | tr '\n' ' ')"
for bgxpid in ${bgxoldgrp} ; do
echo "${bgxjobs}" | grep " ${bgxpid} " >/dev/null 2>&1
if [[ $? -eq 0 ]] ; then
bgxgrp="${bgxgrp} ${bgxpid}"
((bgxcount = bgxcount + 1))
fi
done
}
# bgxlimit - start a sub-process with a limit.
# Loops, calling bgxupdate until there is a free
# slot to run another sub-process. Then runs it
# an updates the process group.
# in: $1 - the limit on processes.
# in: $2+ - the command to run for new process.
# in: bgxgrp - the current group of processes.
# out: bgxgrp - new group of processes
bgxlimit() {
bgxmax=$1 ; shift
bgxupdate
while [[ ${bgxcount} -ge ${bgxmax} ]] ; do
sleep 1
bgxupdate
done
if [[ "$1" != "-" ]] ; then
$* &
bgxgrp="${bgxgrp} $!"
fi
}
# Test program, create group and run 6 sleeps with
# limit of 3.
group1=""
echo 0 $(date | awk '{print $4}') '[' ${group1} ']'
echo
for i in 1 2 3 4 5 6 ; do
bgxgrp=${group1} ; bgxlimit 3 sleep ${i}0 ; group1=${bgxgrp}
echo ${i} $(date | awk '{print $4}') '[' ${group1} ']'
done
# Wait until all others are finished.
echo
bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp}
while [[ ${bgxcount} -ne 0 ]] ; do
oldcount=${bgxcount}
while [[ ${oldcount} -eq ${bgxcount} ]] ; do
sleep 1
bgxgrp=${group1} ; bgxupdate ; group1=${bgxgrp}
done
echo 9 $(date | awk '{print $4}') '[' ${group1} ']'
done
以下は実行例です。
0 12:38:00 [ ]
1 12:38:00 [ 3368 ]
2 12:38:00 [ 3368 5880 ]
3 12:38:00 [ 3368 5880 2524 ]
4 12:38:10 [ 5880 2524 1560 ]
5 12:38:20 [ 2524 1560 5032 ]
6 12:38:30 [ 1560 5032 5212 ]
9 12:38:50 [ 5032 5212 ]
9 12:39:10 [ 5212 ]
9 12:39:30 [ ]
n*10
秒。4番目のプロセスは、最初のプロセスが終了するまで開始されません(時刻t = 10または12:38:10)。 1560が追加される前に、プロセス3368がリストから消えていることがわかります。または、タイムライン形式で:
Process: 1 2 3 4 5 6
-------- - - - - - -
12:38:00 ^ ^ ^
12:38:10 v | | ^
12:38:20 v | | ^
12:38:30 v | | ^
12:38:40 | | |
12:38:50 v | |
12:39:00 | |
12:39:10 v |
12:39:20 |
12:39:30 v
これが最短の方法です。
_waitforjobs() {
while test $(jobs -p | wc -w) -ge "$1"; do wait -n; done
}
_
新しいジョブを分岐する前にこの関数を呼び出します。
_waitforjobs 10
run_another_job &
_
マシンのコアと同じ数のバックグラウンドジョブを使用するには、10などの固定数の代わりに$(nproc)
を使用します。
次のようなコードを記述したいとします。
for x in $(seq 1 100); do # 100 things we want to put into the background.
max_bg_procs 5 # Define the limit. See below.
your_intensive_job &
done
どこ max_bg_procs
を.bashrc
:
function max_bg_procs {
if [[ $# -eq 0 ]] ; then
echo "Usage: max_bg_procs NUM_PROCS. Will wait until the number of background (&)"
echo " bash processes (as determined by 'jobs -pr') falls below NUM_PROCS"
return
fi
local max_number=$((0 + ${1:-0}))
while true; do
local current_number=$(jobs -pr | wc -l)
if [[ $current_number -lt $max_number ]]; then
break
fi
sleep 1
done
}
次の関数(上記のtangensの回答から開発されたもので、スクリプトにコピーするか、ファイルからソースします):
job_limit () {
# Test for single positive integer input
if (( $# == 1 )) && [[ $1 =~ ^[1-9][0-9]*$ ]]
then
# Check number of running jobs
joblist=($(jobs -rp))
while (( ${#joblist[*]} >= $1 ))
do
# Wait for any job to finish
command='wait '${joblist[0]}
for job in ${joblist[@]:1}
do
command+=' || wait '$job
done
eval $command
joblist=($(jobs -rp))
done
fi
}
1)既存のループを制限するために単一の行を挿入するだけです
while :
do
task &
job_limit `nproc`
done
2)ポーリングではなく既存のバックグラウンドタスクの完了を待機するため、高速タスクの効率が向上します。
これはほとんどの目的には十分ですが、最適ではありません。
#!/bin/bash
n=0
maxjobs=10
for i in *.m4a ; do
# ( DO SOMETHING ) &
# limit jobs
if (( $(($((++n)) % $maxjobs)) == 0 )) ; then
wait # wait until all have finished (not optimal, but most times good enough)
echo $n wait
fi
done
純粋なbash以外でこれを実行する場合は、ジョブキューシステムを調べる必要があります。
たとえば、 GNUキュー または [〜#〜] pbs [〜#〜] があります。また、PBSの場合は、設定のために Maui を調べることをお勧めします。
両方のシステムでいくつかの構成が必要になりますが、特定の数のジョブを一度に実行できるようにすることは完全に可能であり、実行中のジョブが終了したときにのみ、新しくキューに入れられたジョブを開始します。通常、これらのジョブキューイングシステムは、特定のバッチジョブに特定の量のメモリまたはコンピューティング時間を割り当てる必要があるスーパーコンピューティングクラスターで使用されます。ただし、計算時間やメモリの制限を考慮せずに、1台のデスクトップコンピューターでこれらのいずれかを使用できない理由はありません。
Linuxでは、これを使用して、bashジョブを使用可能なCPUの数に制限します(CPU_NUMBER
)。
[ "$CPU_NUMBER" ] || CPU_NUMBER="`nproc 2>/dev/null || echo 1`"
while [ "$1" ]; do
{
do something
with $1
in parallel
echo "[$# items left] $1 done"
} &
while true; do
# load the PIDs of all child processes to the array
joblist=(`jobs -p`)
if [ ${#joblist[*]} -ge "$CPU_NUMBER" ]; then
# when the job limit is reached, wait for *single* job to finish
wait -n
else
# stop checking when we're below the limit
break
fi
done
# it's great we executed zero external commands to check!
shift
done
# wait for all currently active child processes
wait
Wait -nなしで行うのは困難です(たとえば、busyboxのシェルはサポートしていません)。したがって、これは回避策です。「ジョブ」および「wc」コマンドを毎秒10回呼び出すため、最適ではありません。たとえば、各ジョブが完了するまで少し長く待つことを気にしない場合は、呼び出しを1秒あたり1倍に減らすことができます。
# $1 = maximum concurent jobs
#
limit_jobs()
{
while true; do
if [ "$(jobs -p | wc -l)" -lt "$1" ]; then break; fi
usleep 100000
done
}
# and now start some tasks:
task &
limit_jobs 2
task &
limit_jobs 2
task &
limit_jobs 2
task &
limit_jobs 2
wait
Bashは主にファイルを1行ずつ処理します。したがって、分割入力ファイルの入力ファイルをN行でキャップすると、単純なパターンが適用されます。
mkdir tmp ; pushd tmp ; split -l 50 ../mainfile.txt
for file in * ; do
while read a b c ; do curl -s http://$a/$b/$c <$file &
done ; wait ; done
popd ; rm -rf tmp;
ulimit -uを使用できます http://ss64.com/bash/ulimit.html を参照してください
10個の長期実行リスナープロセスを開始し、名前付きパイプを介してそれらと通信することを検討しましたか?