Cuatro tareas en paralelo ... ¿cómo hago eso?


23

Tengo un montón de imágenes PNG en un directorio. Tengo una aplicación llamada pngout que ejecuto para comprimir estas imágenes. Esta aplicación es llamada por un script que hice. El problema es que este script hace uno a la vez, algo como esto:

FILES=(./*.png)
for f in  "${FILES[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 $f R${f/\.\//}
done

Procesar solo un archivo a la vez, lleva mucho tiempo. Después de ejecutar esta aplicación, veo que la CPU es solo del 10%. Entonces descubrí que puedo dividir estos archivos en 4 lotes, poner cada lote en un directorio y disparar 4, desde cuatro ventanas de terminal, cuatro procesos, por lo que tengo cuatro instancias de mi script, al mismo tiempo, procesando esas imágenes y el El trabajo toma 1/4 del tiempo.

El segundo problema es que perdí el tiempo dividiendo las imágenes y lotes y copiando el script en cuatro directorios, abriendo 4 ventanas de terminal, bla bla ...

¿Cómo hacer eso con un script, sin tener que dividir nada?

Me refiero a dos cosas: primero, ¿cómo hago desde un script bash, disparo un proceso a un segundo plano? (¿solo agregar & al final?) Segundo: ¿cómo dejo de enviar tareas a un segundo plano después de enviar las cuartas tareas y pongo el script en espera hasta que finalicen las tareas? Quiero decir, ¿simplemente enviar una nueva tarea a un segundo plano cuando finaliza una tarea, manteniendo siempre 4 tareas en paralelo? si no lo hago, el bucle disparará millones de tareas a un segundo plano y la CPU se obstruirá.


Respuestas:


33

Si tiene una copia de la xargsque es compatible con la ejecución paralela -P, simplemente puede hacer

printf '%s\0' *.png | xargs -0 -I {} -P 4 ./pngout -s0 {} R{}

Para otras ideas, el wiki de Wooledge Bash tiene una sección en el artículo de Gestión de Procesos que describe exactamente lo que desea.


2
También hay "gnu parallel" y "xjobs" diseñados para este caso. Es sobre todo una cuestión de gustos que prefieres.
wnoise

¿Podría por favor explicar el comando propuesto? ¡Gracias!
Eugene S

1
@ EugeneS ¿Podría ser un poco más específico sobre qué parte? El printf recopila todos los archivos png y los pasa a través de una tubería a xargs, que recopila argumentos de la entrada estándar y los combina en argumentos para el pngoutcomando que el OP quería ejecutar. La opción clave es -P 4, que le dice a xargs que use hasta 4 comandos simultáneos.
jw013

2
Perdón por no ser preciso. Me interesó específicamente ¿por qué usaste la printffunción aquí en lugar de solo regular ls .. | grep .. *.png? También estaba interesado en los xargsparámetros que usaste ( -0y -I{}). ¡Gracias!
Eugene S

3
@ EugeneS Es para la máxima corrección y robustez. Los nombres de archivo no son líneas y lsno se pueden usar para analizar nombres de archivo de forma portátil y segura . Los únicos caracteres seguros que se pueden usar para delimitar nombres de archivos son \0y /, dado que cualquier otro carácter, incluido \n, puede ser parte del nombre del archivo. Los printfusos \0a los nombres de archivo y los delimitan, -0informa xargsde ello. El -I{}le dice xargsa reemplazar {}con el argumento.
jw013

8

Además de las soluciones ya propuestas, puede crear un archivo MAKE que describa cómo hacer un archivo comprimido sin comprimir y usar make -j 4 para ejecutar 4 trabajos en paralelo. El problema es que necesitará nombrar los archivos comprimidos y sin comprimir de manera diferente, o almacenarlos en diferentes directorios, de lo contrario será imposible escribir una regla de creación razonable.



5

Para responder a sus dos preguntas:

  • Sí, agregar & al final de la línea le indicará a Shell que inicie un proceso en segundo plano.
  • utilizando el waitcomando, puede pedirle al shell que espere a que finalicen todos los procesos en segundo plano antes de continuar.

Aquí está el script modificado para que jse use para realizar un seguimiento de la cantidad de procesos en segundo plano. Cuando NB_CONCURRENT_PROCESSESse alcanza, el script se restablecerá ja 0 y esperará a que finalicen todos los procesos en segundo plano antes de reanudar su ejecución.

files=(./*.png)
nb_concurrent_processes=4
j=0
for f in "${files[@]}"
do
        echo "Processing $f file..."
        # take action on each file. $f store current file name
        ./pngout -s0 "$f" R"${f/\.\//}" &
        ((++j == nb_concurrent_processes)) && { j=0; wait; }
done

1
Esto esperará el último de los cuatro procesos concurrentes y luego iniciará un conjunto de otros cuatro. ¿Quizás uno debería construir una matriz de cuatro PID y luego esperar estos PID específicos?
Nils

Solo para explicar mis correcciones al código: (1) Como cuestión de estilo, evite todos los nombres de variables en mayúscula, ya que potencialmente entran en conflicto con las variables de shell internas. (2) Citas agregadas para $fetc. (3) Uso [para scripts compatibles con POSIX, pero [[siempre se prefiere para bash puro . En este caso, ((es más apropiado para la aritmética.
jw013
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.