Katakanlah saya memiliki loop di Bash:
for foo in `some-command`
do
do-something $foo
done
do-something
terikat cpu dan saya memiliki prosesor 4 inti yang bagus dan berkilau. Saya ingin bisa berlari hingga 4 do-something
sekaligus.
Pendekatan yang naif tampaknya:
for foo in `some-command`
do
do-something $foo &
done
Ini akan menjalankan semua do-something
s sekaligus, tetapi ada beberapa kelemahan, terutama yang melakukan-sesuatu yang juga mungkin memiliki beberapa signifikan I / O yang tampil semua sekaligus mungkin memperlambat sedikit. Masalah lainnya adalah blok kode ini segera kembali, jadi tidak ada cara untuk melakukan pekerjaan lain ketika semua do-something
sudah selesai.
Bagaimana Anda menulis loop ini sehingga selalu ada X do-something
yang berjalan sekaligus?
Jawaban:
Bergantung pada apa yang ingin Anda lakukan, xargs juga dapat membantu (di sini: mengonversi dokumen dengan pdf2ps):
cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w ) find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus pdf2ps
Dari dokumen:
--max-procs=max-procs -P max-procs Run up to max-procs processes at a time; the default is 1. If max-procs is 0, xargs will run as many processes as possible at a time. Use the -n option with -P; otherwise chances are that only one exec will be done.
sumber
find [...] -print0
danxargs -0
.cpus=$(getconf _NPROCESSORS_ONLN)
--max-procs=0
proses sebanyak mungkin?--max-procs=0
lebih seperti upaya penanya (memulai proses sebanyak argumen).Dengan GNU Parallel http://www.gnu.org/software/parallel/ Anda dapat menulis:
GNU Parallel juga mendukung menjalankan pekerjaan pada komputer jarak jauh. Ini akan berjalan satu per inti CPU pada komputer jarak jauh - meskipun mereka memiliki jumlah inti yang berbeda:
Contoh yang lebih canggih: Di sini kami membuat daftar file yang kami ingin my_script untuk dijalankan. File memiliki ekstensi (mungkin .jpeg). Kami ingin output dari my_script diletakkan di sebelah file di basename.out (misalnya foo.jpeg -> foo.out). Kami ingin menjalankan my_script sekali untuk setiap inti yang dimiliki komputer dan kami ingin menjalankannya di komputer lokal juga. Untuk komputer jarak jauh kami ingin file yang akan diproses ditransfer ke komputer yang diberikan. Ketika my_script selesai, kami ingin foo.out ditransfer kembali dan kami ingin foo.jpeg dan foo.out dihapus dari komputer jarak jauh:
cat list_of_files | \ parallel --trc {.}.out -S server1,server2,: \ "my_script {} > {.}.out"
GNU Parallel memastikan keluaran dari setiap pekerjaan tidak tercampur, sehingga Anda dapat menggunakan keluaran sebagai masukan untuk program lain:
Lihat video untuk lebih banyak contoh: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
sumber
find
perintah untuk menghasilkan daftar file, karena tidak hanya mencegah masalah saat ada spasi di dalam nama file yang muncul,for i in ...; do
tetapi find juga dapat melakukan halfind -name \*.extension1 -or -name \*.extension2
yang dapat ditangani oleh GNU paralel {.} Dengan sangat baik.cat
, tentu saja, tidak berguna.sumber
Berikut solusi alternatif yang dapat dimasukkan ke dalam .bashrc dan digunakan untuk satu liner sehari-hari:
function pwait() { while [ $(jobs -p | wc -l) -ge $1 ]; do sleep 1 done }
Untuk menggunakannya, yang harus dilakukan adalah meletakkan
&
setelah pekerjaan dan panggilan pwait, parameter memberikan jumlah proses paralel:for i in *; do do_something $i & pwait 10 done
Ini akan lebih baik untuk digunakan
wait
daripada sibuk menunggu hasiljobs -p
, tetapi tampaknya tidak ada solusi yang jelas untuk menunggu sampai salah satu pekerjaan yang diberikan selesai daripada semuanya.sumber
Alih-alih bash biasa, gunakan Makefile, lalu tentukan jumlah tugas simultan dengan
make -jX
X adalah jumlah tugas yang akan dijalankan sekaligus.Atau Anda dapat menggunakan
wait
("man wait
"): meluncurkan beberapa proses anak, panggilwait
- ini akan keluar saat proses anak selesai.maxjobs = 10 foreach line in `cat file.txt` { jobsrunning = 0 while jobsrunning < maxjobs { do job & jobsrunning += 1 } wait } job ( ){ ... }
Jika Anda perlu menyimpan hasil pekerjaan, maka tetapkan hasilnya ke variabel. Setelah
wait
Anda baru memeriksa apa isi variabel.sumber
Mungkin mencoba utilitas paralel daripada menulis ulang loop? Saya penggemar berat xjobs. Saya menggunakan xjobs sepanjang waktu untuk menyalin file secara massal di seluruh jaringan kami, biasanya saat menyiapkan server database baru. http://www.maier-komor.de/xjobs.html
sumber
Jika Anda terbiasa dengan
make
perintah tersebut, sering kali Anda dapat mengekspresikan daftar perintah yang ingin Anda jalankan sebagai makefile. Misalnya, jika Anda perlu menjalankan $ SOME_COMMAND pada file * .input yang masing-masing menghasilkan * .output, Anda dapat menggunakan makefiledan kemudian lari saja
untuk menjalankan maksimal NUMBER perintah secara paralel.
sumber
Meskipun melakukan ini dengan benar
bash
mungkin tidak mungkin, Anda dapat melakukan semi-kanan dengan cukup mudah.bstark
memberikan perkiraan yang adil tentang hak tetapi miliknya memiliki kekurangan berikut:Perkiraan lain yang tidak memiliki kekurangan ini adalah sebagai berikut:
scheduleAll() { local job i=0 max=4 pids=() for job; do (( ++i % max == 0 )) && { wait "${pids[@]}" pids=() } bash -c "$job" & pids+=("$!") done wait "${pids[@]}" }
Perhatikan bahwa yang satu ini mudah beradaptasi untuk juga memeriksa kode keluar dari setiap pekerjaan saat berakhir sehingga Anda dapat memperingatkan pengguna jika pekerjaan gagal atau menetapkan kode keluar
scheduleAll
sesuai dengan jumlah pekerjaan yang gagal, atau sesuatu.Masalah dengan kode ini hanya itu:
Solusi yang menangani masalah terakhir ini harus digunakan
kill -0
untuk melakukan polling apakah ada proses yang hilang, bukanwait
dan menjadwalkan pekerjaan berikutnya. Namun, itu menimbulkan masalah kecil baru: Anda memiliki kondisi balapan antara pekerjaan berakhir, dankill -0
memeriksa apakah pekerjaan sudah berakhir. Jika pekerjaan berakhir dan proses lain di sistem Anda dimulai pada saat yang sama, mengambil PID acak yang kebetulan merupakan pekerjaan yang baru saja selesai,kill -0
tidak akan melihat pekerjaan Anda telah selesai dan segala sesuatunya akan rusak lagi.Solusi sempurna tidak mungkin dilakukan
bash
.sumber
fungsi untuk pesta:
parallel () { awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\t@-\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make $@ -f - all }
menggunakan:
sumber
make -j
pintar, tetapi tanpa penjelasan dan gumpalan kode Awk hanya-tulis itu, saya menahan diri dari upvoting.Proyek yang saya kerjakan menggunakan perintah tunggu untuk mengontrol proses paralel shell (sebenarnya ksh). Untuk mengatasi kekhawatiran Anda tentang IO, pada OS modern, eksekusi paralel mungkin benar-benar akan meningkatkan efisiensi. Jika semua proses membaca blok yang sama pada disk, hanya proses pertama yang harus menggunakan perangkat keras fisik. Proses lain sering kali dapat mengambil blok dari cache disk OS di memori. Jelas, membaca dari memori beberapa kali lipat lebih cepat daripada membaca dari disk. Selain itu, manfaatnya tidak memerlukan perubahan pengkodean.
sumber
Ini mungkin cukup baik untuk sebagian besar tujuan, tetapi tidak optimal.
#!/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
sumber
Inilah cara saya menyelesaikan masalah ini dalam skrip bash:
#! /bin/bash MAX_JOBS=32 FILE_LIST=($(cat ${1})) echo Length ${#FILE_LIST[@]} for ((INDEX=0; INDEX < ${#FILE_LIST[@]}; INDEX=$((${INDEX}+${MAX_JOBS})) )); do JOBS_RUNNING=0 while ((JOBS_RUNNING < MAX_JOBS)) do I=$((${INDEX}+${JOBS_RUNNING})) FILE=${FILE_LIST[${I}]} if [ "$FILE" != "" ];then echo $JOBS_RUNNING $FILE ./M22Checker ${FILE} & else echo $JOBS_RUNNING NULL & fi JOBS_RUNNING=$((JOBS_RUNNING+1)) done wait done
sumber
Terlambat banget ke pesta di sini, tapi ini solusi lain.
Banyak solusi yang tidak menangani spasi / karakter khusus dalam perintah, tidak menjalankan tugas N setiap saat, memakan cpu dalam loop sibuk, atau bergantung pada dependensi eksternal (misalnya GNU
parallel
).Dengan inspirasi untuk penanganan proses mati / zombie , inilah solusi bash murni:
function run_parallel_jobs { local concurrent_max=$1 local callback=$2 local cmds=("${@:3}") local jobs=( ) while [[ "${#cmds[@]}" -gt 0 ]] || [[ "${#jobs[@]}" -gt 0 ]]; do while [[ "${#jobs[@]}" -lt $concurrent_max ]] && [[ "${#cmds[@]}" -gt 0 ]]; do local cmd="${cmds[0]}" cmds=("${cmds[@]:1}") bash -c "$cmd" & jobs+=($!) done local job="${jobs[0]}" jobs=("${jobs[@]:1}") local state="$(ps -p $job -o state= 2>/dev/null)" if [[ "$state" == "D" ]] || [[ "$state" == "Z" ]]; then $callback $job else wait $job $callback $job $? fi done }
Dan penggunaan sampel:
function job_done { if [[ $# -lt 2 ]]; then echo "PID $1 died unexpectedly" else echo "PID $1 exited $2" fi } cmds=( \ "echo 1; sleep 1; exit 1" \ "echo 2; sleep 2; exit 2" \ "echo 3; sleep 3; exit 3" \ "echo 4; sleep 4; exit 4" \ "echo 5; sleep 5; exit 5" \ ) # cpus="$(getconf _NPROCESSORS_ONLN)" cpus=3 run_parallel_jobs $cpus "job_done" "${cmds[@]}"
Hasil:
Untuk penanganan keluaran per proses
$$
bisa digunakan untuk login ke suatu file, contoh:function job_done { cat "$1.log" } cmds=( \ "echo 1 \$\$ >\$\$.log" \ "echo 2 \$\$ >\$\$.log" \ ) run_parallel_jobs 2 "job_done" "${cmds[@]}"
Keluaran:
sumber
Anda dapat menggunakan loop bersarang sederhana (gantikan bilangan bulat yang sesuai untuk N dan M di bawah):
for i in {1..N}; do (for j in {1..M}; do do_something; done & ); done
Ini akan mengeksekusi do_something N * M kali dalam M putaran, setiap putaran mengeksekusi N pekerjaan secara paralel. Anda dapat membuat N sama dengan jumlah CPU yang Anda miliki.
sumber
Solusi saya untuk selalu menjaga sejumlah proses berjalan, terus melacak kesalahan dan menangani proses ubnterruptible / zombie:
function log { echo "$1" } # Take a list of commands to run, runs them sequentially with numberOfProcesses commands simultaneously runs # Returns the number of non zero exit codes from commands function ParallelExec { local numberOfProcesses="${1}" # Number of simultaneous commands to run local commandsArg="${2}" # Semi-colon separated list of commands local pid local runningPids=0 local counter=0 local commandsArray local pidsArray local newPidsArray local retval local retvalAll=0 local pidState local commandsArrayPid IFS=';' read -r -a commandsArray <<< "$commandsArg" log "Runnning ${#commandsArray[@]} commands in $numberOfProcesses simultaneous processes." while [ $counter -lt "${#commandsArray[@]}" ] || [ ${#pidsArray[@]} -gt 0 ]; do while [ $counter -lt "${#commandsArray[@]}" ] && [ ${#pidsArray[@]} -lt $numberOfProcesses ]; do log "Running command [${commandsArray[$counter]}]." eval "${commandsArray[$counter]}" & pid=$! pidsArray+=($pid) commandsArrayPid[$pid]="${commandsArray[$counter]}" counter=$((counter+1)) done newPidsArray=() for pid in "${pidsArray[@]}"; do # Handle uninterruptible sleep state or zombies by ommiting them from running process array (How to kill that is already dead ? :) if kill -0 $pid > /dev/null 2>&1; then pidState=$(ps -p$pid -o state= 2 > /dev/null) if [ "$pidState" != "D" ] && [ "$pidState" != "Z" ]; then newPidsArray+=($pid) fi else # pid is dead, get it's exit code from wait command wait $pid retval=$? if [ $retval -ne 0 ]; then log "Command [${commandsArrayPid[$pid]}] failed with exit code [$retval]." retvalAll=$((retvalAll+1)) fi fi done pidsArray=("${newPidsArray[@]}") # Add a trivial sleep time so bash won't eat all CPU sleep .05 done return $retvalAll }
Pemakaian:
cmds="du -csh /var;du -csh /tmp;sleep 3;du -csh /root;sleep 10; du -csh /home" # Execute 2 processes at a time ParallelExec 2 "$cmds" # Execute 4 processes at a time ParallelExec 4 "$cmds"
sumber
$ DOMAINS = "daftar beberapa domain dalam perintah" untuk foo in
some-command
doeval `some-command for $DOMAINS` & job[$i]=$! i=$(( i + 1))
selesai
Ndomain =
echo $DOMAINS |wc -w
untuk saya di $ (seq 1 1 $ Ndomains) lakukan echo "tunggu $ {pekerjaan [$ i]}" tunggu "$ {pekerjaan [$ i]}" selesai
dalam konsep ini akan bekerja untuk memparalelkan. Yang terpenting adalah baris terakhir eval adalah '&' yang akan menempatkan perintah ke latar belakang.
sumber