2009-09-21 18 views
41

Me gustaría hacer que una sección de mi código sea más eficiente. Estoy pensando en dividirlo en múltiples procesos y hacer que se ejecuten 50/100 veces a la vez, en lugar de hacerlo solo una vez.Procesos de bifurcación/múltiples roscas | Bash

Por ejemplo (pseudo):

for line in file; 
do 
foo; 
foo2; 
foo3; 
done 

quisiera este bucle para ejecutar varias veces. Sé que esto se puede hacer con bifurcación. ¿Se vería algo como esto?

while(x <= 50) 
parent(child pid) 
{ 
    fork child() 
} 
child 
{ 
    do 
    foo; foo2; foo3; 
    done 
    return child_pid() 
} 

¿O estoy pensando en esto de la manera incorrecta?

Gracias!

Respuesta

2

Vamos a tratar ejemplo

for x in 1 2 3 ; do { echo a $x ; sleep 1 ; echo b $x ; } & done ; sleep 10 

y el uso jobs para ver qué está funcionando.

26

No conozco ninguna llamada explícita fork en bash. Lo que probablemente desee hacer es agregar & a un comando que desee ejecutar en segundo plano. También puede utilizar & en las funciones que se definen dentro de un script bash:

do_something_with_line() 
{ 
    line=$1 
    foo 
    foo2 
    foo3 
} 

for line in file 
do 
    do_something_with_line $line & 
done 

EDITAR: poner un límite en el número de procesos de fondo simultáneas, podría intentar algo como esto:

for line in file 
do 
    while [`jobs | wc -l` -ge 50 ] 
    do 
    sleep 5 
    done 
    do_something_with_line $line & 
done 
+1

Usted ha miscapitalized la hacer_algo ... ;-) nombre –

+1

¡Gracias - ¿Qué pasa cuando quiero para asegurarse de que sólo soy ejecutando 50 instancias a la vez? Y, cuando finalice uno de esos procesos, asegúrese de que se genere 1 más. – Greg

+0

Usa el comando 'jobs' bash incorporado. –

46

En scripts bash (no interactivos) de forma predeterminada JOB CONTROL está deshabilitado, por lo que no puede hacer los comandos: job, fg y bg.

Esto es lo que funciona bien para mí:

#!/bin/sh 

set -m # Enable Job Control 

for i in `seq 30`; do # start 30 jobs in parallel 
    sleep 3 & 
done 

# Wait for all parallel jobs to finish 
while [ 1 ]; do fg 2> /dev/null; [ $? == 1 ] && break; done 

La última línea utiliza "fg" para llevar un trabajo de fondo al primer plano. Hace esto en un bucle hasta que fg devuelva 1 ($? == 1), lo que ocurre cuando ya no hay más trabajos en segundo plano.

+16

En las secuencias de comandos bash, puede usar 'wait', por ejemplo:' sleep 3 & WAITPID = $ !; espere $ WAITPID', o concatene los pids de esta manera 'WAITPIDS =" $ WAITPIDS "$!; ...; espera $ WAITPIDS' –

+9

O simplemente "espera". – lethalman

+0

¿cómo haré 1000 cosas, 50 a la vez?en un bucle de decir '$ (seq 1 1000)' – chovy

17

Con GNU PARALELO que puede hacer:

cat file | parallel 'foo {}; foo2 {}; foo3 {}' 

Esto ejecutará un trabajo en cada núcleo de la CPU. Para ejecutar 50 hacer:

cat file | parallel -j 50 'foo {}; foo2 {}; foo3 {}' 

Vea los videos de introducción para aprender más:

http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

+2

A excepción de la pantalla nag, el paralelo es bastante bueno. –

+1

Yo agregaría que el paralelo ya está instalado en la mayoría de los sistemas. Mi máquina OS X 10.8.5 lo tiene. Es hora de quitar las telarañas de mis guiones de shell y actualizar mis bucles para que entren en paralelo ... – labyrinth

+0

esto parece desordenarse cuando se usan búsquedas/reemplazos que tienen caracteres que deben ser escapados. –

2

Sobre la base de lo que todos compartían yo era capaz de poner esto juntos:

#!/usr/bin/env bash 

VAR1="192.168.1.20 192.168.1.126 192.168.1.36" 

for a in $VAR1; do { ssh -t -t $a -l Administrator "sudo softwareupdate -l"; } & done; 
WAITPIDS="$WAITPIDS "$!;...; wait $WAITPIDS 
echo "Script has finished" 

Exit 1 

Esto enumera todas las actualizaciones en el mac en tres máquinas a la vez. Más tarde lo utilicé para realizar una actualización de software para todas las máquinas cuando tomé mi dirección IP.txt

0

Aquí está mi función de control del hilo:

#!/bin/bash 
# This function just checks jobs in background, don't do more things. 
# if jobs number is lower than MAX, then return to get more jobs; 
# if jobs number is greater or equal to MAX, then wait, until someone finished. 

# Usage: 
# thread_max 8 
# thread_max 0 # wait, until all jobs completed 

thread_max() { 
    local CHECK_INTERVAL="3s" 
    local CUR_THREADS= 
    local MAX= 
    [[ $1 ]] && MAX=$1 || return 127 

    # reset MAX value, 0 is easy to remember 
    [ $MAX -eq 0 ] && { 
     MAX=1 
     DEBUG "waiting for all tasks finish" 
    } 

    while true; do 
     CUR_THREADS=`jobs -p | wc -w` 

     # workaround about jobs bug. If don't execute it explicitily, 
     # CUR_THREADS will stick at 1, even no jobs running anymore. 
     jobs &>/dev/null 

     DEBUG "current thread amount: $CUR_THREADS" 
     if [ $CUR_THREADS -ge $MAX ]; then 
      sleep $CHECK_INTERVAL 
     else 
      return 0 
     fi 
    done 
} 
12

no me gusta usar wait porque se bloquea hasta que termina el proceso, lo cual no es ideal cuando existen múltiples procesos que esperar en lo que pueda' Obtener una actualización de estado hasta que finalice el proceso actual. Prefiero usar una combinación de kill -0 y sleep a esto.

Dada una matriz de pids para esperar, utilizo la siguiente función waitPids() para obtener una retroalimentación continua sobre qué pids aún están pendientes de finalizar.

declare -a pids 
waitPids() { 
    while [ ${#pids[@]} -ne 0 ]; do 
     echo "Waiting for pids: ${pids[@]}" 
     local range=$(eval echo {0..$((${#pids[@]}-1))}) 
     local i 
     for i in $range; do 
      if ! kill -0 ${pids[$i]} 2> /dev/null; then 
       echo "Done -- ${pids[$i]}" 
       unset pids[$i] 
      fi 
     done 
     pids=("${pids[@]}") # Expunge nulls created by unset. 
     sleep 1 
    done 
    echo "Done!" 
} 

Cuando comienzo un proceso en segundo plano, agrego su PID inmediatamente a la matriz pids utilizando esta por debajo de la función de utilidad:

addPid() { 
    desc=$1 
    pid=$2 
    echo "$desc -- $pid" 
    pids=(${pids[@]} $pid) 
} 

Aquí es un ejemplo que muestra cómo utilizar:

for i in {2..5}; do 
    sleep $i & 
    addPid "Sleep for $i" $! 
done 
waitPids 

Y aquí es cómo se ve la retroalimentación:

Sleep for 2 -- 36271 
Sleep for 3 -- 36272 
Sleep for 4 -- 36273 
Sleep for 5 -- 36274 
Waiting for pids: 36271 36272 36273 36274 
Waiting for pids: 36271 36272 36273 36274 
Waiting for pids: 36271 36272 36273 36274 
Done -- 36271 
Waiting for pids: 36272 36273 36274 
Done -- 36272 
Waiting for pids: 36273 36274 
Done -- 36273 
Waiting for pids: 36274 
Done -- 36274 
Done! 
0

El enfoque de haridsv es excelente, brinda la flexibilidad de ejecutar una configuración de ranuras de procesador donde varios procesos se pueden ejecutar con trabajos nuevos que se envían como trabajos completos, manteniendo la carga general. Aquí están mis modificaciones al código de haridsv para un procesador n-slot para una 'grilla' de ngrid 'jobs' (lo uso para grillas de modelos de simulación) Seguido por salida de prueba para 8 trabajos 3 a la vez, con un total acumulado de ejecución , presentado, completado y restante de salida

#!/bin/bash 
######################################################################## 
# see haridsv on forking-multi-threaded-processes-bash 
# loop over grid, submitting jobs in the background. 
# As jobs complete new ones are set going to keep the number running 
# up to n as much as possible, until it tapers off at the end. 
# 
# 8 jobs 
ngrid=8 
# 3 at a time 
n=3 
# running counts 
running=0 
completed=0 
# previous values 
prunning=0 
pcompleted=0 
# 
######################################################################## 
# process monitoring functions 
# 
declare -a pids 
# 
function checkPids() { 
echo ${#pids[@]} 
if [ ${#pids[@]} -ne 0 ] 
then 
    echo "Checking for pids: ${pids[@]}" 
    local range=$(eval echo {0..$((${#pids[@]}-1))}) 
    local i 
    for i in $range; do 
     if ! kill -0 ${pids[$i]} 2> /dev/null; then 
      echo "Done -- ${pids[$i]}" 
      unset pids[$i] 
      completed=$(expr $completed + 1) 
     fi 
    done 
    pids=("${pids[@]}") # Expunge nulls created by unset. 
    running=$((${#pids[@]})) 
    echo "#PIDS :"$running 
fi 
} 
# 
function addPid() { 
    desc=$1 
    pid=$2 
    echo " ${desc} - "$pid 
    pids=(${pids[@]} $pid) 
} 
######################################################################## 
# 
# Loop and report when job changes happen, 
# keep going until all are completed. 
# 
idx=0 
while [ $completed -lt ${ngrid} ] 
do 
# 
    if [ $running -lt $n ] && [ $idx -lt ${ngrid} ] 
    then 
#################################################################### 
# 
# submit a new process if less than n 
# are running and we haven't finished... 
# 
# get desc for process 
# 
     name="job_"${idx} 
# background execution 
     sleep 3 & 
     addPid $name $! 
     idx=$(expr $idx + 1) 
# 
#################################################################### 
# 
    fi 
# 
    checkPids 
# if something changes... 
    if [ ${running} -gt ${prunning} ] || \ 
     [ ${completed} -gt ${pcompleted} ] 
    then 
     remain=$(expr $ngrid - $completed) 
     echo " Running: "${running}" Submitted: "${idx}\ 
       " Completed: "$completed" Remaining: "$remain 
    fi 
# save counts to prev values 
    prunning=${running} 
    pcompleted=${completed} 
# 
    sleep 1 
# 
done 
# 
######################################################################## 

prueba:

job_0 - 75257 
1 
Checking for pids: 75257 
#PIDS :1 
Running: 1 Submitted: 1 Completed: 0 Remaining: 8 
job_1 - 75262 
2 
Checking for pids: 75257 75262 
#PIDS :2 
Running: 2 Submitted: 2 Completed: 0 Remaining: 8 
job_2 - 75267 
3 
Checking for pids: 75257 75262 75267 
#PIDS :3 
Running: 3 Submitted: 3 Completed: 0 Remaining: 8 
3 
Checking for pids: 75257 75262 75267 
Done -- 75257 
#PIDS :2 
Running: 2 Submitted: 3 Completed: 1 Remaining: 7 
job_3 - 75277 
3 
Checking for pids: 75262 75267 75277 
Done -- 75262 
#PIDS :2 
Running: 2 Submitted: 4 Completed: 2 Remaining: 6 
job_4 - 75283 
3 
Checking for pids: 75267 75277 75283 
Done -- 75267 
#PIDS :2 
Running: 2 Submitted: 5 Completed: 3 Remaining: 5 
job_5 - 75289 
3 
Checking for pids: 75277 75283 75289 
#PIDS :3 
Running: 3 Submitted: 6 Completed: 3 Remaining: 5 
3 
Checking for pids: 75277 75283 75289 
Done -- 75277 
#PIDS :2 
Running: 2 Submitted: 6 Completed: 4 Remaining: 4 
job_6 - 75298 
3 
Checking for pids: 75283 75289 75298 
Done -- 75283 
#PIDS :2 
Running: 2 Submitted: 7 Completed: 5 Remaining: 3 
job_7 - 75304 
3 
Checking for pids: 75289 75298 75304 
Done -- 75289 
#PIDS :2 
Running: 2 Submitted: 8 Completed: 6 Remaining: 2 
2 
Checking for pids: 75298 75304 
#PIDS :2 
2 
Checking for pids: 75298 75304 
Done -- 75298 
#PIDS :1 
Running: 1 Submitted: 8 Completed: 7 Remaining: 1 
1 
Checking for pids: 75304 
Done -- 75304 
#PIDS :0 
Running: 0 Submitted: 8 Completed: 8 Remaining: 0 
Cuestiones relacionadas