Ejecutar comandos en paralelo con un límite de número simultáneo de comandos

23

Secuencial: for i in {1..1000}; do do_something $i; donedemasiado lento

Paralelo: for i in {1..1000}; do do_something $i& done- demasiada carga

¿Cómo ejecutar comandos en paralelo, pero no más de, por ejemplo, 20 instancias por momento?

Ahora usualmente usa hack like for i in {1..1000}; do do_something $i& sleep 5; done, pero no es una buena solución

Actualización 2 : convirtió la respuesta aceptada en un script: http://vi-server.org/vi/parallel

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Tenga en cuenta que debe reemplazar 8 espacios con 2 pestañas antes de "i =" para que funcione.

Vi.
fuente

Respuestas:

15

GNU Parallel está hecho para esto.

seq 1 1000 | parallel -j20 do_something

Incluso puede ejecutar trabajos en computadoras remotas. Aquí hay un ejemplo para volver a codificar un MP3 a OGG usando el servidor2 y la computadora local que ejecuta 1 trabajo por núcleo de CPU:

parallel --trc {.}.ogg -j+0 -S server2,: \
     'mpg321 -w - {} | oggenc -q0 - -o {.}.ogg' ::: *.mp3

Vea un video de introducción a GNU Parallel aquí:

http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
fuente
No sé sobre "moreutils" y que ya existe una herramienta para el trabajo. Mirando y comparando.
Vi.
1
El parallelen moreutils no es paralelo GNU y es bastante limitado en sus opciones. El comando anterior no se ejecutará con el paralelo desde moreutils.
Ole Tange
1
Una de las opciones más: xargs --max-procs=20.
Vi.
4

No es una solución bash, pero debe usar un Makefile, posiblemente con -luna carga máxima que no exceda.

NJOBS=1000

.PHONY = jobs
jobs = $(shell echo {1..$(NJOBS)})

all: $(jobs)

$(jobs):
    do_something $@

Luego, para comenzar 20 trabajos a la vez

$ make -j20

o para iniciar tantos trabajos como sea posible sin exceder una carga de 5

$ make -j -l5
Benjamin Bannier
fuente
Parece la solución no hacky por ahora.
Vi.
2
echo -e 'PHONY=jobs\njobs=$(shell echo {1..100000})\n\nall: ${jobs}\n\n${jobs}:\n\t\techo $@; sleep `echo $$RANDOM/6553 | bc -l`' | make -f - -j20Ahora se ve más hacky otra vez.
Vi.
@vi: oh my ....
Benjamin Bannier
Convirtió su solución a un script. Ahora se puede usar con facilidad.
Vi.
2

publicar el script en la pregunta con formato:

#!/bin/bash

NUM=$1; shift

if [ -z "$NUM" ]; then
    echo "Usage: parallel <number_of_tasks> command"
    echo "    Sets environment variable i from 1 to number_of_tasks"
    echo "    Defaults to 20 processes at a time, use like \"MAKEOPTS='-j5' parallel ...\" to override."
    echo "Example: parallel 100 'echo \$i; sleep \`echo \$RANDOM/6553 | bc -l\`'"
    exit 1
fi

export CMD="$@";

true ${MAKEOPTS:="-j20"}

cat << EOF | make -f - -s $MAKEOPTS
PHONY=jobs
jobs=\$(shell echo {1..$NUM})

all: \${jobs}

\${jobs}:
        i=\$@ sh -c "\$\$CMD"
EOF

Tenga en cuenta que debe reemplazar 8 espacios con 2 pestañas antes de "i =".

warren
fuente
1

Una idea simple:

Verifique i modulo 20 y ejecute el comando de espera de shell antes de hacer algo.

harrymc
fuente
Esperará a que se completen todas las tareas actuales (creando pandeos en el diagrama de número de tareas) o esperará una tarea específica que pueda detenerse por más tiempo (nuevamente creando pandeos en este caso)
Vi.
@ Vi: La espera de Shell es para todas las tareas en segundo plano que pertenecen a este shell.
harrymc
1

Puede usar pspara contar cuántos procesos tiene en ejecución, y cada vez que esto cae por debajo de un cierto umbral, inicia otro proceso.

Pseudocódigo:

i = 1
MAX_PROCESSES=20
NUM_TASKS=1000
do
  get num_processes using ps
  if num_processes < MAX_PROCESSES
    start process $i
    $i = $i + 1
  endif
  sleep 1 # add this to prevent thrashing with ps
until $i > NUM_TASKS
Paul R
fuente
1
for i in {1..1000}; do 
     (echo $i ; sleep `expr $RANDOM % 5` ) &
     while [ `jobs | wc -l` -ge 20 ] ; do 
         sleep 1 
     done
done
msw
fuente
Puede ser while [ `jobs | wc -l` -ge 20]; do?
Vi.
Seguro, pero en mi muestra, entonces me tengo que calcular njobsdos veces, y el rendimiento es bastante importante en shell scripts que las tareas de ejecución del sueño;)
msw
Quiero decir que tu versión no funciona como se esperaba. Cómo cambio sleep 1de sleep 0.1y empezar a njobs promedio de 40-50 en lugar de 20. Si hay más de 20 puestos de trabajo que tenemos que esperar a que cualquier trabajo se haya terminado, no sólo tiene que esperar 1 segundo.
Vi.
0

Puedes hacerlo así.

threads=20
tempfifo=$PMS_HOME/$$.fifo

trap "exec 1000>&-;exec 1000<&-;exit 0" 2
mkfifo $tempfifo
exec 1000<>$tempfifo
rm -rf $tempfifo

for ((i=1; i<=$threads; i++))
do
    echo >&1000
done

for ((j=1; j<=1000; j++))
do
    read -u1000
    {
        echo $j
        echo >&1000
    } &
done

wait
echo "done!!!!!!!!!!"

usando tuberías con nombre, cada vez, ejecuta 20 sub shell en paralelo.

Espero que ayude :)

ouyangyewei
fuente