scripts script resueltos programas paso pasar parametros hacer español ejercicios ejemplos como bash parallel-processing

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



scripts linux ejercicios resueltos (11)

Estoy tratando de escribir un archivo .sh que ejecuta muchos programas 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?


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.



Hay un programa 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.


Puedes usar wait :

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

Asigna los PID del programa 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.


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 .

Recientemente tuve una situación similar en la que necesitaba ejecutar múltiples programas 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 <n> permite ejecutar comandos <n> 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 fue el comando que se inició solo después de que el puño del original 3 terminó, 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 programa 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


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

Redirigir los errores para separar los registros.


prog1 & prog2 &