Bagaimana Anda menjalankan beberapa program secara paralel dari skrip bash?


245

Saya mencoba menulis file .sh yang menjalankan banyak program secara bersamaan

Saya mencoba ini

prog1 
prog2

Tapi itu menjalankan prog1 lalu menunggu sampai prog1 berakhir dan kemudian mulai prog2 ...

Jadi bagaimana saya bisa menjalankannya secara paralel?

Jawaban:


216
prog1 &
prog2 &

49
Jangan lupakan wait! Ya, dalam bash Anda bisa menunggu proses anak skrip.
Dummy00001

5
Pilihan lain adalah menggunakan nohupuntuk mencegah program tidak terbunuh ketika shell menutup.
Philipp

@liang: Ya, itu akan bekerja dengan tiga atau lebih program juga.
psmears

302

Bagaimana tentang:

prog1 & prog2 && fg

Ini akan:

  1. Mulai prog1.
  2. Kirim ke latar belakang, tetapi tetap cetak hasilnya.
  3. Mulai prog2, dan simpan di latar depan , sehingga Anda bisa menutupnya ctrl-c.
  4. Ketika Anda dekat prog2, Anda akan kembali ke prog1's latar depan , sehingga Anda dapat juga dekat dengan ctrl-c.

9
Apakah ada cara mudah untuk mengakhiri prog1ketika prog2berakhir? Pikirkan node srv.js & cucumberjs
JP

20
Baru saja mencoba ini, dan itu tidak berhasil seperti yang diharapkan untuk saya. Namun, sedikit modifikasi berhasil: prog1 & prog2 ; fg Ini untuk menjalankan beberapa ssh tunnel sekaligus. Semoga ini bisa membantu seseorang.
jnadro52

2
@ jnadro52 solusi Anda memiliki efek bahwa jika prog2gagal dijalankan segera, Anda akan kembali ke prog1latar depan. Jika ini diinginkan, maka tidak apa-apa.
Ory Band

3
Pada shell SSH'ed Jika Anda menjalankan perintah seperti ini, akan sulit untuk mematikan prog1. Ctrl-c tidak bekerja untuk saya. Bahkan membunuh seluruh terminal membiarkan prog1 berjalan.
mercury0114

14
@ jnadro52 Cara untuk menghentikan kedua proses sekaligus adalah prog1 & prog2 && kill $!.
zaboco

79

Anda bisa menggunakan wait:

some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2

Ini menetapkan PID program latar belakang ke variabel ( $!adalah proses PID yang terakhir diluncurkan), kemudian waitperintah menunggu mereka. Itu bagus karena jika Anda membunuh skrip, itu membunuh proses juga!


4
Dalam pengalaman saya , membunuh menunggu tidak juga membunuh proses lainnya.
Quinn Comendant

1
Jika saya memulai proses latar belakang dalam satu lingkaran bagaimana saya bisa menunggu setiap proses latar belakang untuk menyelesaikan sebelum bergerak maju dengan pelaksanaan set perintah berikutnya. #!/usr/bin/env bash ARRAY='cat bat rat' for ARR in $ARRAY do ./run_script1 $ARR & done P1=$! wait $P1 echo "INFO: Execution of all background processes in the for loop has completed.."
Yash

@Yash Saya pikir Anda dapat menyimpan ID proses ke dalam array, lalu memanggil tunggu di array. Saya pikir Anda harus menggunakan ${}untuk menyisipkannya ke dalam daftar string atau sejenisnya.
trusktr

jawaban terbaik, dan bagi saya membunuh skrip juga membunuh prosesnya! macOS Catalina, zsh console
Michael Klishevich

67

Dengan GNU Parallel http://www.gnu.org/software/parallel/ semudah:

(echo prog1; echo prog2) | parallel

Atau jika Anda lebih suka:

parallel ::: prog1 prog2

Belajarlah lagi:


4
Perlu dicatat bahwa ada beberapa versi paralleldengan sintaks yang berbeda. Sebagai contoh, pada turunan Debian moreutilspaket berisi perintah berbeda yang disebut parallelberperilaku sangat berbeda.
Joel Cross

4
adalah parallellebih baik daripada menggunakan &?
Optimus Prime

2
@OptimusPrime Itu sangat tergantung. GNU Parallel memperkenalkan beberapa overhead, tetapi sebagai imbalannya memberi Anda lebih banyak kontrol atas pekerjaan dan hasil yang sedang berjalan. Jika dua pekerjaan dicetak sekaligus, GNU Parallel akan memastikan output tidak tercampur.
Ole Tange

1
@OptimusPrime parallellebih baik ketika ada lebih banyak pekerjaan daripada inti, dalam hal ini &akan menjalankan lebih dari satu pekerjaan per inti sekaligus. (lih prinsip pigeonhole )
Geremia

2
@OleTange " Baris perintah Anda akan mencintaimu karenanya. " Aku juga. ☺
Geremia

55

Jika Anda ingin dapat dengan mudah menjalankan dan membunuh beberapa proses ctrl-c, ini adalah metode favorit saya: menelurkan banyak proses latar belakang dalam sebuah (…)subkulit, dan perangkap SIGINTuntuk dieksekusi kill 0, yang akan membunuh semua yang dihasilkan dalam grup subkulit:

(trap 'kill 0' SIGINT; prog1 & prog2 & prog3)

Anda dapat memiliki struktur eksekusi proses yang kompleks, dan semuanya akan ditutup dengan satu ctrl-c(pastikan saja proses terakhir dijalankan di latar depan, yaitu, jangan menyertakan &after prog1.3):

(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)

Ini adalah jawaban terbaik sejauh ini.
Nic

10

xargs -P <n>memungkinkan Anda untuk menjalankan <n>perintah secara paralel.

Meskipun -Pmerupakan opsi yang tidak standar, baik implementasi GNU (Linux) dan macOS / BSD mendukungnya.

Contoh berikut:

  • berjalan paling banyak 3 perintah secara paralel pada suatu waktu,
  • dengan perintah tambahan dimulai hanya ketika proses yang diluncurkan sebelumnya berakhir.
time xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF

Outputnya terlihat seperti:

1   # output from 1st command 
4   # output from *last* command, which started as soon as the count dropped below 3
2   # output from 2nd command
3   # output from 3rd command

real    0m3.012s
user    0m0.011s
sys 0m0.008s

Waktu menunjukkan bahwa perintah dijalankan secara paralel (perintah terakhir diluncurkan hanya setelah yang pertama dari 3 asli dihentikan, tetapi dieksekusi sangat cepat).

The xargsperintah itu sendiri tidak akan kembali sampai semua perintah telah selesai, tetapi Anda dapat jalankan di latar belakang dengan mengakhiri itu dengan operator kontrol &dan kemudian menggunakan waitbuiltin untuk menunggu seluruh xargsperintah untuk menyelesaikan.

{
  xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF
} &

# Script execution continues here while `xargs` is running 
# in the background.
echo "Waiting for commands to finish..."

# Wait for `xargs` to finish, via special variable $!, which contains
# the PID of the most recently started background process.
wait $!

catatan:

  • BSD / macOS xargsmengharuskan Anda untuk menentukan jumlah perintah untuk dijalankan secara paralel secara eksplisit , sedangkan GNU xargsmemungkinkan Anda menentukan -P 0untuk menjalankan sebanyak mungkin secara paralel.

  • Output dari proses yang berjalan secara paralel tiba saat sedang dihasilkan , sehingga akan disisipkan secara tak terduga .

    • GNU parallel, sebagaimana disebutkan dalam jawaban Ole ( tidak datang standar dengan sebagian besar platform), dengan mudah membuat serialisasi (mengelompokkan) output berdasarkan per proses dan menawarkan banyak fitur yang lebih canggih.

9
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log

Arahkan ulang kesalahan ke log yang terpisah.


13
Anda harus meletakkan ampersand setelah pengalihan dan meninggalkan titik koma (ampersand juga akan melakukan fungsi pemisah perintah):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
Dijeda sampai pemberitahuan lebih lanjut.

titik koma menjalankan kedua perintah, Anda dapat menguji de bash untuk melihatnya berfungsi dengan baik;) Contoh: pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log ketika Anda meletakkan & Anda meletakkan program di latar belakang dan segera jalankan perintah berikutnya.
fermin

2
Tidak berfungsi - kesalahan tidak diarahkan ke file. Cobalah dengan: ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Kesalahan masuk ke konsol, dan kedua file kesalahan kosong. Seperti yang dikatakan @Dennis Williamson, &adalah pemisah, seperti ;, jadi (a) ia harus pergi di akhir perintah (setelah redirecton), dan (b) Anda tidak memerlukan ;sama sekali :-)
psmears

8

Ada program yang sangat berguna yang memanggil nohup.

     nohup - run a command immune to hangups, with output to a non-tty

4
nohupdengan sendirinya tidak menjalankan apa pun di latar belakang, dan menggunakan nohupbukanlah persyaratan atau prasyarat untuk menjalankan tugas di latar belakang. Mereka sering berguna bersama tetapi dengan demikian, ini tidak menjawab pertanyaan.
tripleee

8

Berikut adalah fungsi yang saya gunakan untuk menjalankan proses max n secara paralel (n = 4 pada contoh):

max_children=4

function parallel {
  local time1=$(date +"%H:%M:%S")
  local time2=""

  # for the sake of the example, I'm using $2 as a description, you may be interested in other description
  echo "starting $2 ($time1)..."
  "$@" && time2=$(date +"%H:%M:%S") && echo "finishing $2 ($time1 -- $time2)..." &

  local my_pid=$$
  local children=$(ps -eo ppid | grep -w $my_pid | wc -w)
  children=$((children-1))
  if [[ $children -ge $max_children ]]; then
    wait -n
  fi
}

parallel sleep 5
parallel sleep 6
parallel sleep 7
parallel sleep 8
parallel sleep 9
wait

Jika max_children diatur ke jumlah core, fungsi ini akan mencoba untuk menghindari core idle.


1
Cuplikan yang bagus, tetapi saya tidak dapat menemukan penjelasan "tunggu-n" di bawah bash saya yang mengatakan itu adalah pilihan yang tidak valid. salah ketik atau apakah saya melewatkan sesuatu?
Emmanuel Devaux

1
@EmmanuelDevaux: wait -nmembutuhkan bash4.3+ dan perubahan logika untuk menunggu setiap proses yang ditentukan / tersirat untuk mengakhiri.
mklement0

bagaimana jika salah satu tugas gagal, maka saya ingin mengakhiri skrip?
52coder

@ 52coder Anda dapat menyesuaikan fungsi untuk menangkap anak yang gagal, seperti: "$ @" && time2 = $ (tanggal + "% H:% S:% S") && echo "menyelesaikan $ 2 ($ time1 - $ time2 ) ... "|| kesalahan = 1 &. Kemudian uji kesalahan pada bagian "jika" dan batalkan fungsi jika diperlukan
arnaldocan

7

Anda dapat mencoba pps . ppss agak kuat - Anda bahkan dapat membuat cluster mini. xargs -P juga dapat berguna jika Anda memiliki banyak pemrosesan paralel yang memalukan untuk dilakukan.


7

Saya memiliki situasi yang sama baru-baru ini di mana saya perlu menjalankan beberapa program pada saat yang sama, mengarahkan output mereka ke file log yang terpisah dan menunggu mereka selesai dan saya berakhir dengan sesuatu seperti itu:

#!/bin/bash

# Add the full path processes to run to the array
PROCESSES_TO_RUN=("/home/joao/Code/test/prog_1/prog1" \
                  "/home/joao/Code/test/prog_2/prog2")
# You can keep adding processes to the array...

for i in ${PROCESSES_TO_RUN[@]}; do
    ${i%/*}/./${i##*/} > ${i}.log 2>&1 &
    # ${i%/*} -> Get folder name until the /
    # ${i##*/} -> Get the filename after the /
done

# Wait for the processes to finish
wait

Sumber: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/


4

Manajer Pemijahan Proses

Tentu, secara teknis ini adalah proses, dan program ini harus benar-benar disebut sebagai proses pemijahan manajer, tetapi ini hanya karena cara BASH bekerja ketika garpu menggunakan ampersand, menggunakan fork () atau mungkin clone () system call yang mengkloning ke ruang memori yang terpisah, daripada sesuatu seperti pthread_create () yang akan berbagi memori. Jika BASH mendukung yang terakhir, setiap "urutan eksekusi" akan beroperasi dengan cara yang sama dan dapat disebut sebagai utas tradisional sekaligus mendapatkan jejak memori yang lebih efisien. Namun secara fungsional kerjanya sama, meskipun sedikit lebih sulit karena variabel GLOBAL tidak tersedia di setiap klon pekerja sehingga penggunaan file komunikasi antar-proses dan semafor kawanan rudimenter untuk mengelola bagian-bagian penting. Mencabuti dari BASH tentu saja merupakan jawaban dasar di sini, tetapi saya merasa seolah-olah orang tahu itu, tetapi benar-benar mencari untuk mengelola apa yang dilahirkan daripada hanya bercabang dan melupakannya. Ini menunjukkan cara untuk mengelola hingga 200 contoh proses bercabang semua yang mengakses satu sumber daya. Jelas ini berlebihan tapi saya senang menulisnya jadi saya teruskan. Tingkatkan ukuran terminal Anda sesuai. Saya harap Anda menemukan ini berguna.

ME=$(basename $0)
IPC="/tmp/$ME.ipc"      #interprocess communication file (global thread accounting stats)
DBG=/tmp/$ME.log
echo 0 > $IPC           #initalize counter
F1=thread
SPAWNED=0
COMPLETE=0
SPAWN=1000              #number of jobs to process
SPEEDFACTOR=1           #dynamically compensates for execution time
THREADLIMIT=50          #maximum concurrent threads
TPS=1                   #threads per second delay
THREADCOUNT=0           #number of running threads
SCALE="scale=5"         #controls bc's precision
START=$(date +%s)       #whence we began
MAXTHREADDUR=6         #maximum thread life span - demo mode

LOWER=$[$THREADLIMIT*100*90/10000]   #90% worker utilization threshold
UPPER=$[$THREADLIMIT*100*95/10000]   #95% worker utilization threshold
DELTA=10                             #initial percent speed change

threadspeed()        #dynamically adjust spawn rate based on worker utilization
{
   #vaguely assumes thread execution average will be consistent
   THREADCOUNT=$(threadcount)
   if [ $THREADCOUNT -ge $LOWER ] && [ $THREADCOUNT -le $UPPER ] ;then
      echo SPEED HOLD >> $DBG
      return
   elif [ $THREADCOUNT -lt $LOWER ] ;then
      #if maxthread is free speed up
      SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1-($DELTA/100))"|bc)
      echo SPEED UP $DELTA%>> $DBG
   elif [ $THREADCOUNT -gt $UPPER ];then
      #if maxthread is active then slow down
      SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1+($DELTA/100))"|bc)
      DELTA=1                            #begin fine grain control
      echo SLOW DOWN $DELTA%>> $DBG
   fi

   echo SPEEDFACTOR $SPEEDFACTOR >> $DBG

   #average thread duration   (total elapsed time / number of threads completed)
   #if threads completed is zero (less than 100), default to maxdelay/2  maxthreads

   COMPLETE=$(cat $IPC)

   if [ -z $COMPLETE ];then
      echo BAD IPC READ ============================================== >> $DBG
      return
   fi

   #echo Threads COMPLETE $COMPLETE >> $DBG
   if [ $COMPLETE -lt 100 ];then
      AVGTHREAD=$(echo "$SCALE;$MAXTHREADDUR/2"|bc)
   else
      ELAPSED=$[$(date +%s)-$START]
      #echo Elapsed Time $ELAPSED >> $DBG
      AVGTHREAD=$(echo "$SCALE;$ELAPSED/$COMPLETE*$THREADLIMIT"|bc)
   fi
   echo AVGTHREAD Duration is $AVGTHREAD >> $DBG

   #calculate timing to achieve spawning each workers fast enough
   # to utilize threadlimit - average time it takes to complete one thread / max number of threads
   TPS=$(echo "$SCALE;($AVGTHREAD/$THREADLIMIT)*$SPEEDFACTOR"|bc)
   #TPS=$(echo "$SCALE;$AVGTHREAD/$THREADLIMIT"|bc)  # maintains pretty good
   #echo TPS $TPS >> $DBG

}
function plot()
{
   echo -en \\033[${2}\;${1}H

   if [ -n "$3" ];then
         if [[ $4 = "good" ]];then
            echo -en "\\033[1;32m"
         elif [[ $4 = "warn" ]];then
            echo -en "\\033[1;33m"
         elif [[ $4 = "fail" ]];then
            echo -en "\\033[1;31m"
         elif [[ $4 = "crit" ]];then
            echo -en "\\033[1;31;4m"
         fi
   fi
      echo -n "$3"
      echo -en "\\033[0;39m"
}

trackthread()   #displays thread status
{
   WORKERID=$1
   THREADID=$2
   ACTION=$3    #setactive | setfree | update
   AGE=$4

   TS=$(date +%s)

   COL=$[(($WORKERID-1)/50)*40]
   ROW=$[(($WORKERID-1)%50)+1]

   case $ACTION in
      "setactive" )
         touch /tmp/$ME.$F1$WORKERID  #redundant - see main loop
         #echo created file $ME.$F1$WORKERID >> $DBG
         plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID INIT    " good
         ;;
      "update" )
         plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID AGE:$AGE" warn
         ;;
      "setfree" )
         plot $COL $ROW "Worker$WORKERID: FREE                         " fail
         rm /tmp/$ME.$F1$WORKERID
         ;;
      * )

      ;;
   esac
}

getfreeworkerid()
{
   for i in $(seq 1 $[$THREADLIMIT+1])
   do
      if [ ! -e /tmp/$ME.$F1$i ];then
         #echo "getfreeworkerid returned $i" >> $DBG
         break
      fi
   done
   if [ $i -eq $[$THREADLIMIT+1] ];then
      #echo "no free threads" >> $DBG
      echo 0
      #exit
   else
      echo $i
   fi
}

updateIPC()
{
   COMPLETE=$(cat $IPC)        #read IPC
   COMPLETE=$[$COMPLETE+1]     #increment IPC
   echo $COMPLETE > $IPC       #write back to IPC
}


worker()
{
   WORKERID=$1
   THREADID=$2
   #echo "new worker WORKERID:$WORKERID THREADID:$THREADID" >> $DBG

   #accessing common terminal requires critical blocking section
   (flock -x -w 10 201
      trackthread $WORKERID $THREADID setactive
   )201>/tmp/$ME.lock

   let "RND = $RANDOM % $MAXTHREADDUR +1"

   for s in $(seq 1 $RND)               #simulate random lifespan
   do
      sleep 1;
      (flock -x -w 10 201
         trackthread $WORKERID $THREADID update $s
      )201>/tmp/$ME.lock
   done

   (flock -x -w 10 201
      trackthread $WORKERID $THREADID setfree
   )201>/tmp/$ME.lock

   (flock -x -w 10 201
      updateIPC
   )201>/tmp/$ME.lock
}

threadcount()
{
   TC=$(ls /tmp/$ME.$F1* 2> /dev/null | wc -l)
   #echo threadcount is $TC >> $DBG
   THREADCOUNT=$TC
   echo $TC
}

status()
{
   #summary status line
   COMPLETE=$(cat $IPC)
   plot 1 $[$THREADLIMIT+2] "WORKERS $(threadcount)/$THREADLIMIT  SPAWNED $SPAWNED/$SPAWN  COMPLETE $COMPLETE/$SPAWN SF=$SPEEDFACTOR TIMING=$TPS"
   echo -en '\033[K'                   #clear to end of line
}

function main()
{
   while [ $SPAWNED -lt $SPAWN ]
   do
      while [ $(threadcount) -lt $THREADLIMIT ] && [ $SPAWNED -lt $SPAWN ]
      do
         WID=$(getfreeworkerid)
         worker $WID $SPAWNED &
         touch /tmp/$ME.$F1$WID    #if this loops faster than file creation in the worker thread it steps on itself, thread tracking is best in main loop
         SPAWNED=$[$SPAWNED+1]
         (flock -x -w 10 201
            status
         )201>/tmp/$ME.lock
         sleep $TPS
        if ((! $[$SPAWNED%100]));then
           #rethink thread timing every 100 threads
           threadspeed
        fi
      done
      sleep $TPS
   done

   while [ "$(threadcount)" -gt 0 ]
   do
      (flock -x -w 10 201
         status
      )201>/tmp/$ME.lock
      sleep 1;
   done

   status
}

clear
threadspeed
main
wait
status
echo

0

Script Anda akan terlihat seperti:

prog1 &
prog2 &
.
.
progn &
wait
progn+1 &
progn+2 &
.
.

Dengan asumsi sistem Anda dapat mengambil pekerjaan sekaligus. gunakan tunggu untuk menjalankan hanya n pekerjaan sekaligus.


-1

Dengan bashj ( https://sourceforge.net/projects/bashj/ ), Anda harus dapat menjalankan tidak hanya beberapa proses (seperti yang disarankan orang lain) tetapi juga beberapa Thread dalam satu JVM yang dikendalikan dari skrip Anda. Tapi tentu saja ini membutuhkan java JDK. Thread mengkonsumsi sumber daya lebih sedikit daripada proses.

Ini adalah kode yang berfungsi:

#!/usr/bin/bashj

#!java

public static int cnt=0;

private static void loop() {u.p("java says cnt= "+(cnt++));u.sleep(1.0);}

public static void startThread()
{(new Thread(() ->  {while (true) {loop();}})).start();}

#!bashj

j.startThread()

while [ j.cnt -lt 4 ]
do
  echo "bash views cnt=" j.cnt
  sleep 0.5
done
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.