実行するタスクが10以上あり、システムは同時に最大4つのタスクを実行できると制限しています。
私のタスクは次のように開始できます:myprog taskname
これらのタスクを実行するbashシェルスクリプトを作成するにはどうすればよいですか。最も重要なことは、1つのタスクが終了すると、スクリプトがすぐに別のタスクを開始できるため、実行中のタスクの数が常に4のままになるということです。
私は自分のプロセスプールを作成しているときにこのスレッドを偶然見つけ、特に信号が正しく機能しなかったのでBrandon Horsleyのソリューションが好きだったので、Apacheからインスピレーションを得て、次のようにfifoを使用してプリフォークモデルを試すことにしました私のジョブキュー。
次の関数は、ワーカープロセスがフォークされたときに実行される関数です。
# \brief the worker function that is called when we fork off worker processes
# \param[in] id the worker ID
# \param[in] job_queue the fifo to read jobs from
# \param[in] result_log the temporary log file to write exit codes to
function _job_pool_worker()
{
local id=$1
local job_queue=$2
local result_log=$3
local line=
exec 7<> ${job_queue}
while [[ "${line}" != "${job_pool_end_of_jobs}" && -e "${job_queue}" ]]; do
# workers block on the exclusive lock to read the job queue
flock --exclusive 7
read line <${job_queue}
flock --unlock 7
# the worker should exit if it sees the end-of-job marker or run the
# job otherwise and save its exit code to the result log.
if [[ "${line}" == "${job_pool_end_of_jobs}" ]]; then
# write it one more time for the next sibling so that everyone
# will know we are exiting.
echo "${line}" >&7
else
_job_pool_echo "### _job_pool_worker-${id}: ${line}"
# run the job
{ ${line} ; }
# now check the exit code and prepend "ERROR" to the result log entry
# which we will use to count errors and then strip out later.
local result=$?
local status=
if [[ "${result}" != "0" ]]; then
status=ERROR
fi
# now write the error to the log, making sure multiple processes
# don't trample over each other.
exec 8<> ${result_log}
flock --exclusive 8
echo "${status}job_pool: exited ${result}: ${line}" >> ${result_log}
flock --unlock 8
exec 8>&-
_job_pool_echo "### _job_pool_worker-${id}: exited ${result}: ${line}"
fi
done
exec 7>&-
}
Githubで 私のソリューションのコピーを取得 できます。これが私の実装を使用したサンプルプログラムです。
#!/bin/bash
. job_pool.sh
function foobar()
{
# do something
true
}
# initialize the job pool to allow 3 parallel jobs and echo commands
job_pool_init 3 0
# run jobs
job_pool_run sleep 1
job_pool_run sleep 2
job_pool_run sleep 3
job_pool_run foobar
job_pool_run foobar
job_pool_run /bin/false
# wait until all jobs complete before continuing
job_pool_wait
# more jobs
job_pool_run /bin/false
job_pool_run sleep 1
job_pool_run sleep 2
job_pool_run foobar
# don't forget to shut down the job pool
job_pool_shutdown
# check the $job_pool_nerrors for the number of jobs that exited non-zero
echo "job_pool_nerrors: ${job_pool_nerrors}"
お役に立てれば!
xargs
を使用:
xargs -P <maximun-number-of-process-at-a-time> -n <arguments per process> <commnad>
詳細 こちら 。
GNU Parallelを使用すると、次のことができます。
cat tasks | parallel -j4 myprog
コアが4つある場合は、次のようにすることもできます。
cat tasks | parallel myprog
http://git.savannah.gnu.org/cgit/parallel.git/tree/README から:
GNU Parallelの完全インストールは次のように簡単です:
./configure && make && make install
Rootでない場合は、パスに〜/ binを追加して、〜/ binおよび〜/ shareにインストールできます。
./configure --prefix=$HOME && make && make install
または、システムに「make」がない場合は、src/parallel src/sem src/niceload src/sqlをパスのディレクトリにコピーするだけです。
パラレルが必要で、「make」がインストールされていない場合(システムが古いか、Microsoft Windowsである可能性があります):
wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem
mv parallel sem dir-in-your-$PATH/bin/
この後、あなたはできるはずです:
parallel -j0 ping -nc 3 ::: foss.org.my gnu.org freenetproject.org
これにより、3つのpingパケットが3つの異なるホストに同時に送信され、完了時に出力が出力されます。
簡単な紹介については、紹介ビデオをご覧ください: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
4つのスクリプトを書くことをお勧めします。各スクリプトは、一定数のタスクを連続して実行します。次に、4つのスクリプトを並行して開始する別のスクリプトを記述します。たとえば、script1.sh、script2.sh、script3.sh、script4.shのスクリプトがある場合、headscript.shと呼ばれるスクリプトを使用できます。
#!/bin/sh
./script1.sh &
./script2.sh &
./script3.sh &
./script4.sh &
以下 @ Parag Sardas ' の回答と、ここにリンクされているドキュメントは、.bash_aliases
に追加する簡単なスクリプトです。
doc link を再リンクすることは読む価値があるためです
#!/bin/bash
# https://stackoverflow.com/a/19618159
# https://stackoverflow.com/a/51861820
#
# Example file contents:
# touch /tmp/a.txt
# touch /tmp/b.txt
if [ "$#" -eq 0 ]; then
echo "$0 <file> [max-procs=0]"
exit 1
fi
FILE=${1}
MAX_PROCS=${2:-0}
cat $FILE | while read line; do printf "%q\n" "$line"; done | xargs --max-procs=$MAX_PROCS -I CMD bash -c CMD
つまり./xargs-parallel.sh jobs.txt 4
jobs.txtから読み取られる最大4つのプロセス
このテスト済みスクリプトは一度に5つのジョブを実行し、実行するとすぐに新しいジョブを再開します(SIGCHLDを取得したときのスリープ10.9の強制終了によります。これの簡単なバージョンでは、直接ポーリングを使用できます(スリープ10.9を1をスリープ状態にし、トラップを取り除く)。
#!/usr/bin/bash
set -o monitor
trap "pkill -P $$ -f 'sleep 10\.9' >&/dev/null" SIGCHLD
totaljobs=15
numjobs=5
worktime=10
curjobs=0
declare -A pidlist
dojob()
{
slot=$1
time=$(echo "$RANDOM * 10 / 32768" | bc -l)
echo Starting job $slot with args $time
sleep $time &
pidlist[$slot]=`jobs -p %%`
curjobs=$(($curjobs + 1))
totaljobs=$(($totaljobs - 1))
}
# start
while [ $curjobs -lt $numjobs -a $totaljobs -gt 0 ]
do
dojob $curjobs
done
# Poll for jobs to die, restarting while we have them
while [ $totaljobs -gt 0 ]
do
for ((i=0;$i < $curjobs;i++))
do
if ! kill -0 ${pidlist[$i]} >&/dev/null
then
dojob $i
break
fi
done
sleep 10.9 >&/dev/null
done
wait
あなたはおそらく信号で巧妙な何かをすることができます。
これは概念を説明するためだけのものであり、十分にテストされていないことに注意してください。
#!/usr/local/bin/bash
this_pid="$$"
jobs_running=0
sleep_pid=
# Catch alarm signals to adjust the number of running jobs
trap 'decrement_jobs' SIGALRM
# When a job finishes, decrement the total and kill the sleep process
decrement_jobs()
{
jobs_running=$(($jobs_running - 1))
if [ -n "${sleep_pid}" ]
then
kill -s SIGKILL "${sleep_pid}"
sleep_pid=
fi
}
# Check to see if the max jobs are running, if so sleep until woken
launch_task()
{
if [ ${jobs_running} -gt 3 ]
then
(
while true
do
sleep 999
done
) &
sleep_pid=$!
wait ${sleep_pid}
fi
# Launch the requested task, signalling the parent upon completion
(
"$@"
kill -s SIGALRM "${this_pid}"
) &
jobs_running=$((${jobs_running} + 1))
}
# Launch all of the tasks, this can be in a loop, etc.
launch_task task1
launch_task tast2
...
launch_task task99
4つのシェルスクリプトに関する他の回答は、すべてのタスクがほぼ同じ時間を要すると想定しているため、手動で設定する必要があるため、私を完全には満足させません。しかし、ここに私がそれを改善する方法があります。
メインスクリプトは、特定のnamimg規則に従って実行可能ファイルへのシンボリックリンクを作成します。例えば、
ln -s executable1 ./01-task.01
最初のプレフィックスはソート用で、サフィックスはバッチ(01-04)を識別します。次に、バッチ番号を入力として受け取り、次のような処理を行う4つのシェルスクリプトを生成します。
for t in $(ls ./*-task.$batch | sort ; do
t
rm t
done
Bashでのジョブプールの実装を見てください: https://github.com/spektom/Shell-utils/blob/master/jp.sh
たとえば、多数のURLからダウンロードするときに最大3つのcURLプロセスを実行するには、次のようにcURLコマンドをラップできます。
./jp.sh "My Download Pool" 3 curl http://site1/...
./jp.sh "My Download Pool" 3 curl http://site2/...
./jp.sh "My Download Pool" 3 curl http://site3/...
...
これで紹介した方法に基づいて変更を加えました Bashでプロセスプールを書き込む 。
#!/bin/bash
#set -e # this doesn't work here for some reason
POOL_SIZE=4 # number of workers running in parallel
#######################################################################
# populate jobs #
#######################################################################
declare -a jobs
for (( i = 1988; i < 2019; i++ )); do
jobs+=($i)
done
echo '################################################'
echo ' Launching jobs'
echo '################################################'
parallel() {
local proc procs jobs cur
jobs=("$@") # input jobs array
declare -a procs=() # processes array
cur=0 # current job idx
morework=true
while $morework; do
# if process array size < pool size, try forking a new proc
if [[ "${#procs[@]}" -lt "$POOL_SIZE" ]]; then
if [[ $cur -lt "${#jobs[@]}" ]]; then
proc=${jobs[$cur]}
echo "JOB ID = $cur; JOB = $proc."
###############
# do job here #
###############
sleep 3 &
# add to current running processes
procs+=("$!")
# move to the next job
((cur++))
else
morework=false
continue
fi
fi
for n in "${!procs[@]}"; do
kill -0 "${procs[n]}" 2>/dev/null && continue
# if process is not running anymore, remove from array
unset procs[n]
done
done
wait
}
parallel "${jobs[@]}"
これが私の解決策です。アイデアは非常に簡単です。セマフォとしてfifo
を作成します。各行は使用可能なリソースを表します。キューをread
ingするとき、何も残っていない場合、メインプロセスはブロックします。そして、タスクが完了した後、キューに何かをecho
ingするだけでリソースを返します。
_function task() {
local task_no="$1"
# doing the actual task...
echo "Executing Task ${task_no}"
# which takes a long time
sleep 1
}
function execute_concurrently() {
local tasks="$1"
local ps_pool_size="$2"
# create an anonymous fifo as a Semaphore
local sema_fifo
sema_fifo="$(mktemp -u)"
mkfifo "${sema_fifo}"
exec 3<>"${sema_fifo}"
rm -f "${sema_fifo}"
# every 'x' stands for an available resource
for i in $(seq 1 "${ps_pool_size}"); do
echo 'x' >&3
done
for task_no in $(seq 1 "${tasks}"); do
read dummy <&3 # blocks util a resource is available
(
trap 'echo x >&3' EXIT # returns the resource on exit
task "${task_no}"
)&
done
wait # wait util all forked tasks have finished
}
execute_concurrently 10 4
_
上記のスクリプトは、10個のタスクと4個のタスクを同時に実行します。 $(seq 1 "${tasks}")
シーケンスを、実行する実際のタスクキューに変更できます。
私は A Foo Walks into a Bar ... blog で提案された最良の解決策を見つけましたxargsツールまず、実行するコマンドのリストを含むファイルcommands.txtを作成します
myprog taskname1
myprog taskname2
myprog taskname3
myprog taskname4
...
myprog taskname123
次に、それを次のようにxargsにパイプして、4つのプロセスプールで実行します。
cat commands.txt | xargs -I CMD --max-procs=4 bash -c CMD
プロセスを変更することはできません