Hoe voer je meerdere programma’s parallel uit vanuit een bash-script?

Ik probeer een .sh-bestandte schrijven dat veel programma’s gelijktijdig

uitvoert

Ik heb dit geprobeerd

prog1 
prog2

Maar dat start prog1 en wacht dan tot prog1 eindigt en start dan prog2…

Dus hoe kan ik ze parallel laten lopen?


Antwoord 1, autoriteit 100%

Wat dacht je van:

prog1 & prog2 && fg

Dit zal:

  1. Start prog1.
  2. Stuur het naar de achtergrond, maar blijf de uitvoer afdrukken.
  3. Start prog2en houd het op de voorgrond, zodat je het kunt sluiten met ctrl-c.
  4. Als je prog2sluit, keer je terug naar de voorgrondvan prog1, dus je kunt het ook sluiten met ctrl-c.

Antwoord 2, autoriteit 81%

Om meerdere programma’s parallel te laten lopen:

prog1 &
prog2 &

Als u wilt dat uw script wacht tot de programma’s klaar zijn, kunt u het volgende toevoegen:

wait

op het punt waar u wilt dat het script op hen wacht.


Antwoord 3, autoriteit 35%

Als je gemakkelijk meerdere processen wilt kunnen uitvoeren en doden met ctrl-c, is dit mijn favoriete methode: meerdere achtergrondprocessen spawnen in een (…)subshell, en trap SIGINTin om kill 0uit te voeren, wat alles zal doden dat in de subshell-groep is voortgekomen:

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

U kunt complexe structuren voor procesuitvoering hebben, en alles wordt afgesloten met een enkele ctrl-c(zorg er wel voor dat het laatste proces op de voorgrond wordt uitgevoerd, dwz voeg geen &na prog1.3):

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

Antwoord 4, autoriteit 28%

U kunt waitgebruiken:

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

Het wijst de PID’s van het achtergrondprogramma toe aan variabelen ($!is de PID van het laatst gestarte proces), waarna de opdracht waiterop wacht. Het is leuk, want als je het script doodt, worden ook de processen vernietigd!


Antwoord 5, autoriteit 21%

Met GNU Parallel http://www.gnu.org/software/parallel/het is zo eenvoudig als:

(echo prog1; echo prog2) | parallel

Of als je wilt:

parallel ::: prog1 prog2

Meer informatie:


Antwoord 6, autoriteit 2%

Je kunt ppssproberen (verlaten). ppss is vrij krachtig – je kunt zelfs een minicluster maken.
xargs -P kan ook handig zijn als je een reeks beschamende parallelle verwerkingen te doen hebt.


Antwoord 7

Processpawning-manager

Natuurlijk, technisch gezien zijn dit processen, en dit programma zou eigenlijk een process spawning manager moeten worden genoemd, maar dit komt alleen door de manier waarop BASH werkt wanneer het forks gebruikt met het ampersand, het gebruikt de fork() of misschien clone( ) systeemaanroep die naar een aparte geheugenruimte kloont, in plaats van iets als pthread_create() dat geheugen zou delen. Als BASH dit laatste zou ondersteunen, zou elke “uitvoeringsvolgorde” precies hetzelfde werken en zou het traditionele threads kunnen worden genoemd, terwijl het een efficiëntere geheugenvoetafdruk zou krijgen. Functioneel werkt het echter hetzelfde, hoewel een beetje moeilijker omdat GLOBAL-variabelen niet beschikbaar zijn in elke werkkloon, vandaar het gebruik van het communicatiebestand tussen processen en de rudimentaire kudde-semafoor om kritieke secties te beheren. Forking van BASH is natuurlijk het basisantwoord hier, maar ik heb het gevoel dat mensen dat weten, maar echt willen beheren wat er wordt voortgebracht in plaats van het gewoon te forken en het te vergeten. Dit demonstreert een manier om tot 200 instanties van gevorkte processen te beheren die allemaal toegang hebben tot één enkele bron. Dit is duidelijk overdreven, maar ik vond het leuk om het te schrijven, dus ik ging door. Vergroot de grootte van uw terminal dienovereenkomstig. Ik hoop dat je dit nuttig vindt.

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

Other episodes