¿Cómo se ejecutan múltiples progtwigs en paralelo desde un script bash?

Estoy tratando de escribir un archivo .sh que ejecuta muchos progtwigs simultáneamente

Intenté esto

prog1 prog2 

Pero eso corre prog1 luego espera hasta que prog1 finalice y luego se inicia prog2 …

Entonces, ¿cómo puedo ejecutarlos en paralelo?

 prog1 & prog2 & 

Qué tal si:

 prog1 & prog2 && fg 

Esta voluntad:

  1. Comience prog1 .
  2. Envíelo a fondo, pero siga imprimiendo su salida.
  3. Inicie prog2 y manténgalo en primer plano para que pueda cerrarlo con ctrl-c .
  4. Cuando cierre prog2 , volverá al primer plano del prog1 , por lo que también puede cerrarlo con ctrl-c .

Con GNU Parallel http://www.gnu.org/software/parallel/ es tan fácil como:

 (echo prog1; echo prog2) | parallel 

O si lo prefiere:

 parallel ::: prog1 prog2 

Aprende más:

Puedes usar wait :

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

Asigna los PID del progtwig en segundo plano a las variables ( $! Es el último proceso lanzado ‘PID), luego el comando wait espera. Es bueno porque si matas el script, también mata los procesos.

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

Redirigir los errores para separar los registros.

Hay un progtwig muy útil que llama nohup.

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

Puedes probar ppss . ppss es bastante poderoso, incluso puedes crear un mini-cluster. xargs -P también puede ser útil si tienes un lote de procesamiento vergonzosamente paralelo que hacer.

Aquí hay una función que uso para ejecutar el proceso max n en paralelo (n = 4 en el ejemplo):

 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 

Si max_children está configurado en la cantidad de núcleos, esta función intentará evitar los núcleos inactivos.

Recientemente tuve una situación similar en la que necesitaba ejecutar múltiples progtwigs al mismo tiempo, redirigir sus salidas a archivos de registro separados y esperar a que terminen y terminé con algo así:

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

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

xargs -P permite ejecutar comandos en paralelo.

Mientras -P es una opción no estándar, las implementaciones GNU (Linux) y macOS / BSD lo admiten.

El siguiente ejemplo:

  • ejecuta como máximo 3 comandos en paralelo a la vez,
  • con comandos adicionales que comienzan solo cuando finaliza un proceso iniciado anteriormente.
 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 

El resultado se ve algo como:

 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 

El tiempo muestra que los comandos se ejecutaron en paralelo (el último comando se inició solo después de que terminó el primero de los 3 originales, pero se ejecutó muy rápidamente).

El comando xargs sí no regresará hasta que todos los comandos hayan finalizado, pero puede ejecutarlo en segundo plano al terminarlo con el operador de control y luego usar el comando de espera para esperar a que termine todo el comando 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 $! 

Nota:

  • BSD / macOS xargs requiere que especifique el recuento de comandos para ejecutar en paralelo explícitamente , mientras que los xargs GNU le permiten especificar -P 0 para ejecutar tantos como sea posible en paralelo.

  • La salida de los procesos que se ejecutan en paralelo llega a medida que se genera , por lo que se intercalará imprevisiblemente .

    • El parallel GNU, como se menciona en la respuesta de Ole ( no viene de manera estándar con la mayoría de las plataformas), serializa (agrupa) convenientemente la salida por proceso y ofrece muchas más funciones avanzadas.

Gerente de desove de procesos

Claro, técnicamente estos son procesos, y este progtwig realmente debería llamarse administrador de generación de procesos, pero esto solo se debe a la forma en que BASH funciona cuando se bifurca utilizando el signo y, usa la llamada al sistema fork () o quizás clone () que se clona en un espacio de memoria separado, en lugar de algo como pthread_create () que compartiría memoria. Si BASH soportaba este último, cada “secuencia de ejecución” funcionaría de la misma manera y podría denominarse como hilos tradicionales a la vez que obtendría una huella de memoria más eficiente. Funcionalmente, sin embargo, funciona igual, aunque es un poco más difícil ya que las variables GLOBAL no están disponibles en cada clon de trabajador, por lo tanto, el uso del archivo de comunicación entre procesos y el semáforo flock rudimentario para gestionar secciones críticas. Bifurcar de BASH, por supuesto, es la respuesta básica aquí, pero siento como si la gente lo supiera, pero realmente está buscando administrar lo que se genera en lugar de simplemente bifurcarlo y olvidarlo. Esto demuestra una forma de administrar hasta 200 instancias de procesos bifurcados, todos accediendo a un único recurso. Claramente esto es excesivo pero disfruté escribiéndolo así que seguí. Aumente el tamaño de su terminal en consecuencia. Espero que encuentres esto útil.

 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 quieres poder ejecutar y matar fácilmente múltiples procesos con ctrl-c , este es mi método favorito: engendrar múltiples procesos de fondo en una (…) subshell, y capturar SIGINT para ejecutar kill 0 , lo que matará a todo lo generado en el grupo subshell:

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

Puede tener estructuras de ejecución de procesos complejas, y todo se cerrará con un solo ctrl-c (solo asegúrese de que el último proceso se ejecute en primer plano):

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

Con bashj ( https://sourceforge.net/projects/bashj/ ), debería ser capaz de ejecutar no solo múltiples procesos (de la manera que otros sugirieron) sino también múltiples hilos en una JVM controlada desde su script. Pero, por supuesto, esto requiere un java JDK. Los hilos consumen menos recursos que los procesos.

Aquí hay un código de trabajo:

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