2011-07-06 10 views
21

Tengo un gran conjunto de archivos para los que es necesario realizar un gran procesamiento. Este procesamiento en una sola hebra, utiliza unos cientos de MiB de RAM (en la máquina utilizada para iniciar el trabajo) y tarda unos minutos en ejecutarse. Mi uso actual es iniciar un trabajo de hadoop en los datos de entrada, pero he tenido el mismo problema en otros casos anteriormente.Ejecutando un número limitado de procesos secundarios en paralelo en bash?

Para aprovechar al máximo la potencia de la CPU disponible, quiero poder ejecutar varias de esas tareas en paralelo.

Sin embargo, un muy simple script de ejemplo concha como esto puede mandar al traste el rendimiento del sistema debido a una carga excesiva e intercambio:

find . -type f | while read name ; 
do 
    some_heavy_processing_command ${name} & 
done 

Así que lo que quiero es esencialmente similar a lo que "gmake -j4" lo hace.

Sé que bash admite el comando "esperar" pero solo espera hasta que se completen todos los procesos secundarios. En el pasado he creado scripts que hacen un comando 'ps' y luego grep el niño procesa por su nombre (sí, lo sé ... feo).

¿Cuál es la solución más simple/limpia/mejor para hacer lo que quiero?


Edit: Gracias a Frederik: Sí, efectivamente se trata de un duplicado de How to limit number of threads/sub-processes used in a function in bash Los "xargs --max-procs = 4" funciona como un encanto. (Así que votaron para cerrar mi propia pregunta)

+8

posible duplicado de http://stackoverflow.com/questions/6511884/how-to-limit-number-of-threads-used-in-a-function-in-bash que haría uso de 'xargs --max-procs = 4' para esto ... –

+4

parece un trabajo para [GNU paralelo] (http://www.gnu.org/software/parallel/), pero no estoy seguro de que agregue más poder para 'xargs --max-procs', que no sabía – larsen

+0

@Niels: He estado usando' screen' para este propósito, aunque es un poco desordenado de esta manera, especialmente cuando se inicia desde dentro de otra 'pantalla' sesión;) – 0xC0000022L

Respuesta

18
#! /usr/bin/env bash 

set -o monitor 
# means: run background processes in a separate processes... 
trap add_next_job CHLD 
# execute add_next_job when we receive a child complete signal 

todo_array=($(find . -type f)) # places output into an array 

index=0 
max_jobs=2 

function add_next_job { 
    # if still jobs to do then add one 
    if [[ $index -lt ${#todo_array[*]} ]] 
    # apparently stackoverflow doesn't like bash syntax 
    # the hash in the if is not a comment - rather it's bash awkward way of getting its length 
    then 
     echo adding job ${todo_array[$index]} 
     do_job ${todo_array[$index]} & 
     # replace the line above with the command you want 
     index=$(($index+1)) 
    fi 
} 

function do_job { 
    echo "starting job $1" 
    sleep 2 
} 

# add initial set of jobs 
while [[ $index -lt $max_jobs ]] 
do 
    add_next_job 
done 

# wait for all jobs to complete 
wait 
echo "done" 

Dicho esto hace que el Fredrik excelente punto de que xargs hace exactamente lo que quiere ...

+0

Ahora entiendo el código, pero tuve que pensar un poco. Especialmente la parte acerca de por qué estos se ejecutarían en paralelo (bueno, porque son subprocesos) me eludió. Creo que valdría la pena agregar comentarios para esa parte en el código también. – 0xC0000022L

+0

Aunque mi aplicación actual funciona muy bien con xargs --max-procs, todavía te doy el crédito de ser "la respuesta" porque tu script se puede usar en más situaciones. Gracias. –

3

Este código funcionó bastante bien para mí.

Me di cuenta de un problema por el cual la secuencia de comandos no podía finalizar. Si se encuentra con un caso en el que el script no finaliza debido a que max_jobs es mayor que la cantidad de elementos en el conjunto, el script nunca se cerrará.

Para evitar el escenario anterior, agregué lo siguiente justo después de la declaración "max_jobs".

if [ $max_jobs -gt ${#todo_array[*]} ]; 
    then 
      # there are more elements found in the array than max jobs, setting max jobs to #of array elements" 
      max_jobs=${#todo_array[*]} 
fi 
20

Sé que llego tarde a la fiesta con esta respuesta, pero pensé que iba a publicar una alternativa que, en mi humilde opinión, hace que el cuerpo de la aspiradora guión y más simple. (Es evidente que usted puede cambiar los valores de 2 & 5 por ser apropiados para su escenario.)

function max2 { 
    while [ `jobs | wc -l` -ge 2 ] 
    do 
     sleep 5 
    done 
} 

find . -type f | while read name ; 
do 
    max2; some_heavy_processing_command ${name} & 
done 
wait 
+2

¡Amigo, esto funciona brillantemente! ¡Gracias! :) – mkgrunder

+0

Esto funcionó para mí después de cambiar la sintaxis while a: while [$ (jobs | wc -l) -ge 2] –

4

creo encontró una solución más práctica usando :

#!/usr/bin/make -f 

THIS := $(lastword $(MAKEFILE_LIST)) 
TARGETS := $(shell find . -name '*.sh' -type f) 

.PHONY: all $(TARGETS) 

all: $(TARGETS) 

$(TARGETS): 
     some_heavy_processing_command [email protected] 

$(THIS): ; # Avoid to try to remake this makefile 

Llámalo como p. 'test.mak' y añada derechos de ejecución. Si llama al ./test.mak llamará al some_heavy_processing_command uno a uno. Pero puede llamar como ./test.mak -j 4, luego ejecutará cuatro subprocesos a la vez.También puede usarlo de una manera más sofisticada: ejecute como ./test.mak -j 5 -l 1.5, luego ejecutará un máximo de 5 subprocesos mientras la carga del sistema sea inferior a 1,5, pero limitará el número de procesos si la carga del sistema excede 1.5.

Es más flexible que , y es parte de la distribución estándar, no como parallel.

-1

Otra opción:

PARALLEL_MAX=... 
function start_job() { 
    while [ $(ps --no-headers -o pid --ppid=$$ | wc -l) -gt $PARALLEL_MAX ]; do 
    sleep .1 # Wait for background tasks to complete.       
    done 
    "[email protected]" & 
} 
start_job some_big_command1 
start_job some_big_command2 
start_job some_big_command3 
start_job some_big_command4 
... 
-1

Aquí es una función muy buena He utilizado para controlar el máximo # de puestos de trabajo de bash o ksh. NOTA: el - 1 en el pgrep resta el subproceso wc -l.

function jobmax 
{ 
    typeset -i MAXJOBS=$1 
    sleep .1 
    while ((($(pgrep -P $$ | wc -l) - 1) >= $MAXJOBS)) 
    do 
     sleep .1 
    done 
} 

nproc=5 
for i in {1..100} 
do 
    sleep 1 & 
    jobmax $nproc 
done 
wait # Wait for the rest 
Cuestiones relacionadas