Como você executa vários programas em paralelo a partir de um script bash?

Eu estou tentando escrever um arquivo .sh que executa muitos programas simultaneamente

Eu tentei isso

prog1 prog2 

Mas isso roda o prog1 então espera até que o prog1 termine e então inicie o prog2 …

Então, como posso executá-los em paralelo?

     prog1 & prog2 & 

    E se:

     prog1 & prog2 && fg 

    Isso vai:

    1. Inicie o prog1 .
    2. Envie para o segundo plano, mas continue imprimindo a saída.
    3. Inicie o prog2 e mantenha-o em primeiro plano , para poder fechá-lo com ctrl-c .
    4. Quando você fechar o prog2 , você retornará ao primeiro plano do prog1 , então você também pode fechá-lo com ctrl-c .

    Com o GNU Parallel http://www.gnu.org/software/parallel/ , é tão fácil quanto:

     (echo prog1; echo prog2) | parallel 

    Ou se preferir:

     parallel ::: prog1 prog2 

    Saber mais:

    Você pode usar a wait :

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

    Ele atribui os PIDs do programa em segundo plano às variables ​​( $! É o último processo iniciado ‘PID) e, em seguida, o comando wait aguarda por eles. É legal porque se você matar o script, ele mata os processos também!

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

    Redirecionar erros para separar logs.

    Existe um programa muito útil que chama nohup.

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

    Você pode tentar o ppss . O ppss é bastante poderoso – você pode até criar um mini-cluster. xargs -P também pode ser útil se você tiver um lote de processamento embaraçosamente paralelo para fazer.

    Aqui está uma function que eu uso para executar no máximo n processo em paralelo (n = 4 no exemplo):

     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 

    Se max_children estiver configurado para o número de núcleos, essa function tentará evitar núcleos inativos.

    Eu tive uma situação semelhante recentemente, onde precisei executar vários programas ao mesmo tempo, redirect suas saídas para arquivos de log separados e esperar que eles terminassem e acabei com algo assim:

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

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

    xargs -P permite que você execute comandos em paralelo.

    Enquanto -P é uma opção não padrão, tanto o GNU (Linux) quanto as implementações macOS / BSD suportam isso.

    O exemplo a seguir:

    • executa no máximo 3 comandos em paralelo por vez,
    • com comandos adicionais iniciados somente quando um processo iniciado anteriormente termina.
     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 

    A saída parece algo gosta:

     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 

    A temporização mostra que os comandos foram executados em paralelo (o último comando foi lançado somente após o primeiro dos 3 originais ter sido finalizado, mas executado muito rapidamente).

    O comando xargs si não retornará até que todos os comandos tenham terminado, mas você pode executá-lo em segundo plano encerrando-o com o operador de controle & e depois usando o wait builtin para aguardar a conclusão do comando xargs inteiro.

     { 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:

    • O BSD / macOS xargs requer que você especifique a contagem de comandos para executar em paralelo explicitamente , enquanto que o GNU xargs permite que você especifique -P 0 para executar o maior número possível em paralelo.

    • Saída dos processos executados em paralelo chega como está sendo gerado , por isso será imprevisível intercalado .

      • GNU parallel , como mencionado na resposta da Ole (não é padrão na maioria das plataformas), convenientemente serializa (agrupa) a saída em uma base por processo e oferece muitos resources mais avançados.

    Gerente de desova de processo

    Claro, tecnicamente, estes são processos, e este programa deve realmente ser chamado de gerenciador de desova de processo, mas isso é devido apenas ao modo como o BASH funciona quando ele usa o E comercial, ele usa a chamada de sistema fork () ou talvez clone () que clona em um espaço de memory separado, em vez de algo como pthread_create () que compartilharia a memory. Se a BASH suportasse a última, cada “seqüência de execução” operaria da mesma forma e poderia ser denominada como threads tradicionais, enquanto ganhava uma pegada de memory mais eficiente. Funcionalmente, no entanto, funciona da mesma forma, embora um pouco mais difícil, já que as variables ​​GLOBAL não estão disponíveis em cada clone de trabalho, portanto, o uso do arquivo de comunicação entre processos e o semáforo rudimentar para gerenciar seções críticas. A bifurcação da BASH, é claro, é a resposta básica aqui, mas eu sinto como se as pessoas soubessem disso, mas estão realmente olhando para gerenciar o que é gerado, em vez de apenas bifurcá-lo e esquecê-lo. Isso demonstra uma maneira de gerenciar até 200 instâncias de processos bifurcados, todos acessando um único recurso. Claramente isso é um exagero, mas eu gostei de escrever, então eu continuei. Aumente o tamanho do seu terminal de acordo. Espero que você ache isso ú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 

    Se você quiser ser capaz de rodar e matar facilmente múltiplos processos com ctrl-c , este é o meu método favorito: gerar múltiplos processos em segundo plano em um subshell (…) , e capturar SIGINT para executar kill 0 , que matará tudo gerado no grupo subshell:

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

    Você pode ter estruturas complexas de execução de processos, e tudo será fechado com um único ctrl-c (apenas certifique-se de que o último processo seja executado em primeiro plano):

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

    Com o bashj ( https://sourceforge.net/projects/bashj/ ), você deve ser capaz de executar não apenas múltiplos processos (da maneira como os outros sugeriram), mas também múltiplos Threads em uma JVM controlada a partir do seu script. Mas é claro que isso requer um Java JDK. Threads consomem menos resources do que processos.

    Aqui está um código de trabalho:

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