Paralelizar el script Bash con el número máximo de procesos

Resuelto thelsdj asked hace 16 años • 16 respuestas

Digamos que tengo un bucle en Bash:

for foo in `some-command`
do
   do-something $foo
done

do-somethingestá vinculado a la CPU y tengo un bonito y brillante procesador de 4 núcleos. Me gustaría poder ejecutar hasta 4 do-somethinga la vez.

El enfoque ingenuo parece ser:

for foo in `some-command`
do
   do-something $foo &
done

Esto se ejecutará todos do-something a la vez, pero hay un par de desventajas, principalmente que hacer algo también puede tener algunas E/S significativas que, si se ejecutan todos a la vez, podrían ralentizarse un poco. El otro problema es que este bloque de código regresa inmediatamente, por lo que no hay forma de hacer otro trabajo cuando todos los do-somethingmensajes hayan terminado.

¿Cómo escribirías este bucle para que siempre haya X do-somethingejecutándose a la vez?

thelsdj avatar Sep 01 '08 23:09 thelsdj
Aceptado

Dependiendo de lo que quieras hacer, xargs también puede ayudar (aquí: convertir documentos con pdf2ps):

cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )

find . -name \*.pdf | xargs --max-args=1 --max-procs=$cpus  pdf2ps

De los documentos:

--max-procs=max-procs
-P max-procs
       Run up to max-procs processes at a time; the default is 1.
       If max-procs is 0, xargs will run as many processes as  possible  at  a
       time.  Use the -n option with -P; otherwise chances are that only one
       exec will be done.
Fritz G. Mehner avatar May 19 '2009 07:05 Fritz G. Mehner

Con GNU Parallel http://www.gnu.org/software/parallel/ puedes escribir:

some-command | parallel do-something

GNU Parallel también admite la ejecución de trabajos en computadoras remotas. Esto ejecutará uno por núcleo de CPU en las computadoras remotas, incluso si tienen una cantidad diferente de núcleos:

some-command | parallel -S server1,server2 do-something

Un ejemplo más avanzado: aquí enumeramos los archivos en los que queremos que se ejecute my_script. Los archivos tienen extensión (tal vez .jpeg). Queremos que la salida de my_script se coloque junto a los archivos en basename.out (por ejemplo, foo.jpeg -> foo.out). Queremos ejecutar my_script una vez para cada núcleo que tenga la computadora y también queremos ejecutarlo en la computadora local. Para las computadoras remotas queremos que el archivo se procese y se transfiera a la computadora dada. Cuando my_script finalice, queremos que foo.out se vuelva a transferir y luego queremos que foo.jpeg y foo.out se eliminen de la computadora remota:

cat list_of_files | \
parallel --trc {.}.out -S server1,server2,: \
"my_script {} > {.}.out"

GNU Parallel se asegura de que la salida de cada trabajo no se mezcle, por lo que puede usar la salida como entrada para otro programa:

some-command | parallel do-something | postprocess

Vea los videos para ver más ejemplos: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ole Tange avatar Jun 10 '2010 01:06 Ole Tange

Aquí hay una solución alternativa que se puede insertar en .bashrc y usar para una línea diaria:

function pwait() {
    while [ $(jobs -p | wc -l) -ge $1 ]; do
        sleep 1
    done
}

Para usarlo, todo lo que hay que hacer es poner &después de los trabajos y una llamada pwait, el parámetro da el número de procesos paralelos:

for i in *; do
    do_something $i &
    pwait 10
done

Sería mejor usarlo waiten lugar de estar ocupado esperando la salida de jobs -p, pero no parece haber una solución obvia para esperar hasta que finalice alguno de los trabajos dados en lugar de todos.

Grumbel avatar May 19 '2009 03:05 Grumbel