Comment exécutez-vous plusieurs programmes en parallèle à partir d’un script bash?

J’essaie d’écrire un fichier .sh qui exécute plusieurs programmes simultanément

J’ai essayé ça

prog1 prog2 

Mais qui lance prog1 attend alors la fin du prog1 puis commence le prog2 …

Alors, comment puis-je les exécuter en parallèle?

 prog1 & prog2 & 

Que diriez-vous:

 prog1 & prog2 && fg 

Cette volonté:

  1. Démarrer prog1 .
  2. Envoyez-le en arrière-plan, mais continuez à imprimer ses résultats.
  3. Lancez prog2 et conservez-le au premier plan pour pouvoir le fermer avec ctrl-c .
  4. Lorsque vous fermez prog2 , vous revenez au premier plan de prog1 , vous pouvez donc le fermer avec ctrl-c .

Avec GNU Parallel http://www.gnu.org/software/parallel/, c’est aussi simple que:

 (echo prog1; echo prog2) | parallel 

Ou si vous préférez:

 parallel ::: prog1 prog2 

Apprendre encore plus:

Vous pouvez utiliser wait :

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

Il assigne les PID du programme en arrière-plan aux variables ( $! Est le dernier PID du processus lancé), puis la commande d’attente les attend. C’est sympa car si vous tuez le script, ça tue aussi les processus!

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

Rediriger les erreurs pour séparer les journaux.

Il existe un programme très utile qui appelle nohup.

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

Vous pouvez essayer les ppss . ppss est plutôt puissant – vous pouvez même créer un mini-cluster. xargs -P peut également être utile si vous avez un lot de parallel processing embarrassant à faire.

Voici une fonction que j’utilise pour exécuter max n process en parallèle (n = 4 dans l’exemple):

 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 interestd 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 

Si max_children est défini sur le nombre de cœurs, cette fonction essaiera d’éviter les cœurs inactifs.

J’ai eu récemment une situation similaire où je devais exécuter plusieurs programmes en même temps, redirect leurs sorties vers des fichiers journaux séparés et attendre qu’ils se terminent et j’ai abouti à quelque chose comme ça:

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

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

xargs -P vous permet d’exécuter des commandes en parallèle.

Bien que l’option -P soit une option non standard, les implémentations GNU (Linux) et macOS / BSD le prennent en charge.

L’exemple suivant:

  • exécute au maximum 3 commandes en parallèle à la fois,
  • avec des commandes supplémentaires démarrant uniquement lorsqu’un processus lancé précédemment se termine.
 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 

La sortie ressemble à quelque chose:

 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 

Le timing montre que les commandes ont été exécutées en parallèle (la dernière commande a été lancée uniquement après la fin du premier des 3 originaux, mais exécutée très rapidement).

La commande xargs elle-même ne sera pas retournée tant que toutes les commandes ne seront pas terminées, mais vous pourrez l'exécuter en arrière-plan en la terminant avec un opérateur de contrôle & puis en utilisant l' wait interne pour attendre la fin de la commande xargs .

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

Remarque:

  • BSD / macOS xargs nécessite que vous xargs explicitement le nombre de commandes à exécuter en parallèle, alors que GNU xargs vous permet de spécifier -P 0 pour exécuter autant que possible en parallèle.

  • La sortie des processus exécutés en parallèle arrive au fur et à mesure de sa génération , elle sera donc nestede de manière imprévisible .

    • GNU parallel , comme mentionné dans la réponse d'Ole (n'est pas standard avec la plupart des plates-formes), sérialise (regroupe) commodément les sorties par processus et offre de nombreuses fonctionnalités plus avancées.

Process Spawning Manager

Bien sûr, techniquement, ce sont des processus, et ce programme devrait vraiment être appelé un gestionnaire de processus, mais cela est uniquement dû à la façon dont BASH fonctionne lorsqu’il utilise l’esperluette, utilise l’appel système fork () ou peut-être clone () qui clone dans un espace mémoire distinct, plutôt que quelque chose comme pthread_create () qui partagerait la mémoire. Si BASH supportait cette dernière, chaque “séquence d’exécution” fonctionnerait de la même manière et pourrait être qualifiée de thread traditionnel tout en obtenant une empreinte mémoire plus efficace. Fonctionnellement cependant, cela fonctionne de la même manière, bien qu’un peu plus difficile puisque les variables GLOBAL ne sont pas disponibles dans chaque clone de travail, d’où l’utilisation du fichier de communication inter-processus et du sémaphore flocké pour gérer les sections critiques. Bien sûr, Forking from BASH est la réponse de base ici, mais je pense que les gens le savent, mais cherchent vraiment à gérer ce qui est généré plutôt que de simplement le laisser tomber et l’oublier. Cela démontre une façon de gérer jusqu’à 200 instances de processus fourchus qui accèdent toutes à une seule ressource. Clairement, c’est exagéré, mais j’ai aimé l’écrire, alors j’ai continué. Augmentez la taille de votre terminal en conséquence. J’espère que vous trouvez ça utile.

 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 

Si vous voulez pouvoir exécuter et tuer facilement plusieurs processus avec ctrl-c , c’est ma méthode préférée: générer plusieurs processus d’arrière-plan dans un sous-shell (…) , et piéger SIGINT pour qu’il exécute kill 0 , ce qui tuera tout groupe de sous-shell:

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

Vous pouvez avoir des structures d’exécution de processus complexes, et tout se fermera avec un simple ctrl-c (assurez-vous que le dernier processus est exécuté au premier plan):

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

Avec bashj ( https://sourceforge.net/projects/bashj/ ), vous devriez pouvoir exécuter non seulement plusieurs processus (comme d’autres l’ont suggéré) mais aussi plusieurs threads dans une JVM contrôlée à partir de votre script. Mais bien sûr, cela nécessite un JDK Java. Les threads utilisent moins de ressources que les processus.

Voici un code de travail:

 #!/usr/bin/bashj #!java public static int cnt=0; private static void loop() {up("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