Bir bash betiğinden birden çok programı paralel olarak nasıl çalıştırıyorsunuz?


245

Aynı anda birçok programı çalıştıran bir .sh dosyası yazmaya çalışıyorum

Bunu denedim

prog1 
prog2

Ama bu prog1'i çalıştırır sonra prog1 bitene kadar bekler ve sonra prog2 başlar ...

Peki onları paralel olarak nasıl çalıştırabilirim?

Yanıtlar:


216
prog1 &
prog2 &

49
Unutma wait! Evet, bashta komut dosyasının alt süreçlerini bekleyebilirsiniz.
Dummy00001

5
Başka bir seçenek, nohupkabuk kapandığında programın öldürülmesini önlemek için kullanmaktır .
Philipp

@ liang: Evet, üç veya daha fazla programla da çalışacak.
psmears

302

Nasıl olur:

prog1 & prog2 && fg

Bu irade:

  1. Başlat prog1.
  2. Arka plana gönderin, ancak çıktısını yazdırmaya devam edin.
  3. Başlayın prog2ve ön planda tutun , böylece ile kapatabilirsiniz ctrl-c.
  4. Ne zaman seni yakın prog2, sen dönersiniz prog1bireyin ön planda da yakın onunla can bu kadar, ctrl-c.

9
Sonlandırmak için kolay bir yol var mı prog1ne zaman prog2sona erer? Düşün node srv.js & cucumberjs
JP

20
Sadece bunu denedim ve benim için beklendiği gibi çalışmadı. Ancak, küçük bir değişiklik işe yaradı: prog1 & prog2 ; fg Bu, aynı anda birden fazla ssh tüneli çalıştırmak içindi. Umarım bu birine yardımcı olur.
jnadro52

2
@ jnadro52 çözümünüz prog2hemen çalışmazsa prog1, ön planda olmaya geri döneceksiniz . Bu arzu edilirse, o zaman sorun değil.
Ory Band

3
SSH'nin kabuğunda Böyle bir komut yürütürseniz, prog1'i öldürmek zor olacaktır. Ctrl-c benim için çalışmadı. Hatta tüm terminali öldürüyor prog1 çalışıyor.
mercury0114

14
@ jnadro52 Her iki işlemi de aynı anda sonlandırmanın bir yolu prog1 & prog2 && kill $!.
zaboco

79

Şunları kullanabilirsiniz wait:

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

Arka plan programı PID'lerini değişkenlere atar ( $!son başlatılan işlem 'PID'dir), sonra waitkomut onları bekler. Güzel çünkü senaryoyu öldürürseniz, süreçleri de öldürür!


4
Deneyimlerime göre , beklemeyi öldürmek diğer süreçleri de öldürmez.
Quinn Comendant

1
Bir döngüde arka plan işlemlerine başlıyorsam, bir sonraki komut kümesinin yürütülmesi ile ilerlemeden önce her arka plan işleminin tamamlanmasını nasıl bekleyebilirim. #!/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 Sanırım süreç kimlikleri bir diziye kaydedebilir, sonra dizi üzerinde bekle çağrısı yapabilirsiniz. Bence ${}bir dize listesi veya benzeri içine enterpolasyon yapmak için kullanmak zorunda.
trusktr

en iyi cevap, ve benim için senaryoyu öldürmek de süreçleri öldürüyor! macOS Catalina, zsh konsolu
Michael Klishevich

67

GNU Parallel http://www.gnu.org/software/parallel/ ile bu kadar kolay:

(echo prog1; echo prog2) | parallel

İsterseniz:

parallel ::: prog1 prog2

Daha fazla bilgi edin:


4
parallelFarklı sözdizimine sahip farklı sürümleri olduğunu belirtmek gerekir . Örneğin, Debian türevlerinde moreutilspaket, paralleloldukça farklı davranan adlandırılan farklı bir komut içerir .
Joel Cross

4
olduğu parallelkullanmaktan daha iyi &?
Optimus Prime

2
@OptimusPrime Gerçekten bağlıdır. GNU Parallel bazı ek yükler getirir, ancak karşılığında çalışan işler ve çıktılar üzerinde çok daha fazla kontrol sağlar. İki iş aynı anda yazdırılırsa, GNU Parallel çıktının karışık olmadığından emin olur.
Ole Tange

1
@OptimusPrime parallel, çekirdeklerden daha fazla iş olduğunda daha iyidir, bu durumda &çekirdek başına bir kerede birden fazla iş çalışır. (bkz. güvercin deliği ilkesi )
Geremia

2
@OleTange " Komut satırınız sizi bunun için sevecek. " Ben de. ☺
Geremia

55

Birden çok işlemi kolayca çalıştırmak ve öldürmek istiyorsanız ctrl-c, bu benim en sevdiğim yöntemdir: bir (…)alt kabukta birden fazla arka plan işlemi üretin ve SIGINTyürütmek için tuzak kill 0, alt kabuk grubunda ortaya çıkan her şeyi öldürür:

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

Karmaşık süreç yürütme yapılara sahip olabilir ve her şey yakın tek sahip olacaktır ctrl-c(sadece yani emin geçen süreç ön planda çalıştırılan yapmak, bir içermez &SONRA prog1.3):

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

Bu en iyi cevap.
Nic

10

xargs -P <n><n>komutları paralel olarak çalıştırmanızı sağlar .

-PStandart olmayan bir seçenek olsa da, hem GNU (Linux) hem de macOS / BSD uygulamaları bunu destekler.

Aşağıdaki örnek:

  • bir seferde en fazla 3 komut paralel olarak çalışır ,
  • ek komutlar yalnızca daha önce başlatılan bir işlem sona erdiğinde başlar.
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

Çıktı şöyle görünür:

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

Zamanlama, komutların paralel olarak çalıştırıldığını gösterir (son komut yalnızca orijinal 3'ün ilkinden sonra başlatıldı, ancak çok hızlı bir şekilde yürütüldü).

xargsTüm komutlar bitinceye kadar komut kendisi döndürmez, ancak kontrol operatörü ile sonlandırarak arka planda çalıştırabilirsiniz &kullanarak sonra ve waittüm beklemek yerleşiğini xargskomutu bitirmek için.

{
  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 $!

Not:

  • BSD / macOS xargs, paralel olarak açıkça çalıştırılacak komut sayısını belirtmenizi gerektirirken , GNU paralel olarak olabildiğince fazla çalışmayı xargsbelirtmenize izin verir .-P 0

  • Paralel olarak çalışan işlemlerden elde edilen çıktı üretilirken gelir , bu nedenle beklenmedik şekilde serpiştirilir .

    • GNU parallelbelirtildiği gibi, Ole'nın cevap (yok değil elverişli en platformlarla standart geliyor) serializes (grup) başına işlem bazında ve teklifler daha birçok gelişmiş özellikler üzerinde çıktı.

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

Hataları günlükleri ayırmak için yönlendirin.


13
Ve işaretleri yeniden yönlendirmelerden sonra koymanız ve noktalı virgül bırakmanız gerekir (ve işareti ayrıca bir komut ayırıcının işlevini de yerine getirir):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
sonraki duyuruya kadar duraklatıldı.

noktalı virgül her iki komodu da yürütürse, iyi çalıştığını görmek için de bash'ı test edebilirsiniz;) Örnek: pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log programı koyduğunuzda ve programı arka plana koyduğunuzda ve hemen bir sonraki komutu çalıştırdığınızda.
fermin

2
Çalışmıyor - hatalar dosyaya yönlendirilmiyor. İle deneyin: ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Hatalar konsola gider ve her iki hata dosyası boştur. @Dennis Williamson'ın dediği gibi &, bir ayırıcı, ;yani, (a) komutun sonuna (herhangi bir yeniden yönlendirme işleminden sonra) gitmesi gerekiyor ve (b) hiç ihtiyacınız yok ;:-)
psmears

8

Nohup çağıran çok kullanışlı bir program var.

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

4
nohuptek başına arka planda hiçbir şey çalıştırmaz ve kullanmak nohup, arka planda görevleri çalıştırmak için bir gereklilik veya önkoşul değildir. Genellikle birlikte faydalıdırlar, ancak bu soruya cevap vermez.
Üçlü

8

Paralel olarak maksimum n işleminde (örnekte n = 4) çalıştırmak için kullandığım bir fonksiyon:

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

Max_children çekirdek sayısına ayarlanmışsa, bu işlev boş çekirdeklerden kaçınmaya çalışır.


1
Güzel snippet, ama bash altında "wait -n" açıklamasını bulamıyorum geçersiz bir seçenek olduğunu söylüyor. yazım hatası mı yoksa bir şey mi kaçırdım?
Emmanuel Devaux

1
@EmmanuelDevaux: wait -ngerektirir bash4.3+ ve bekleyen mantık değiştirir herhangi sonlandırmak için belirtilen / ima süreçlerin.
mklement0

görevlerden biri başarısız olursa, komut dosyalarını sonlandırmak istiyorum?
52coder

@ 52 kodlayıcı, başarısız bir çocuğu yakalamak için işlevi ayarlayabilirsiniz: "$ @" && time2 = $ (tarih + "% H:% M:% S") && echo "2 $ ($ time1 - $ time2) ) ... "|| hata = 1 &. Sonra "if" bölümünde hata olup olmadığını test edin ve gerekirse işlevi iptal edin
arnaldocan

7

Pps'leri deneyebilirsiniz . ppss oldukça güçlüdür - bir mini küme bile oluşturabilirsiniz. xargs -P, utanç verici bir şekilde paralel işlem kümeniz varsa da yararlı olabilir.


7

Geçenlerde aynı anda birden fazla program çalıştırmak, çıktılarını ayrılmış günlük dosyalarına yönlendirmek ve bitirmelerini beklemek zorunda kaldım benzer bir durum vardı ve böyle bir şey ile sona erdi:

#!/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

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


4

Proses Yumurtlama Müdürü

Elbette, teknik olarak bunlar süreçlerdir ve bu program gerçekten bir süreç yumurtlama yöneticisi olarak adlandırılmalıdır, ancak bu sadece BASH'in ve işareti kullanarak çatalladığında çalışması nedeniyle fork () veya belki clone () sistem çağrısını kullanır. bellek paylaşacak olan pthread_create () gibi bir şey yerine, ayrı bir bellek alanına klonlar. BASH ikincisini destekleseydi, her bir "yürütme dizisi" aynı şekilde çalışır ve daha verimli bir bellek alanı elde edilirken geleneksel iş parçacıkları olarak adlandırılabilir. Bununla birlikte, her işçi klonunda GLOBAL değişkenleri bulunmadığından, kritik bölümleri yönetmek için süreçler arası iletişim dosyasının ve ilkel akın semaforunun kullanılması nedeniyle işlevsel olarak aynı çalışır, ancak biraz daha zor. Tabii ki BASH'den çatallama burada temel cevap ama sanırım insanlar bunu biliyor ama gerçekten sadece çatal ve yumurtlamak yerine yumurtladı ne yönetmek istiyoruz hissediyorum. Bu, tümü tek bir kaynağa erişen çatallı işlemlerin 200 örneğini yönetmenin bir yolunu gösterir. Açıkçası bu overkill ama ben devam etti, bu yüzden yazma zevk. Terminalinizin boyutunu buna göre artırın. Umarım bunu faydalı bulursun.

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

Senaryonuz şöyle görünmelidir:

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

Sisteminizin bir seferde n iş alabileceğini varsayarsak. bir seferde yalnızca n iş çalıştırmak için wait kullanın.


-1

İle bashj ( https://sourceforge.net/projects/bashj/ ) kullanarak, sadece birden çalıştırmak mümkün olmalıdır süreçleri (yön diğerleri önerilen) değil, aynı zamanda birden fazla konu Betiğiniz kontrollü bir JVM. Ama elbette bu bir java JDK gerektirir. İş parçacıkları süreçlerden daha az kaynak tüketir.

İşte çalışan bir kod:

#!/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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.