Paralelizar el script Bash con el número máximo de procesos
Digamos que tengo un bucle en Bash:
for foo in `some-command`
do
do-something $foo
done
do-something
está vinculado a la CPU y tengo un bonito y brillante procesador de 4 núcleos. Me gustaría poder ejecutar hasta 4 do-something
a 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-something
mensajes hayan terminado.
¿Cómo escribirías este bucle para que siempre haya X do-something
ejecutándose a la vez?
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.
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
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 wait
en 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.