Mínimo & # 8220; Cola de tareas & # 8221; con herramientas de Linux en stock para aprovechar la CPU multinúcleo
-
06-07-2019 - |
Pregunta
¿Cuál es la forma mejor / más fácil de construir un sistema de cola de tareas mínimo para Linux usando bash y herramientas comunes?
Tengo un archivo con 9'000 líneas, cada línea tiene una línea de comando bash, los comandos son completamente independientes.
command 1 > Logs/1.log
command 2 > Logs/2.log
command 3 > Logs/3.log
...
Mi caja tiene más de un núcleo y quiero ejecutar X tareas al mismo tiempo. Busqué en la web una buena manera de hacer esto. Aparentemente, muchas personas tienen este problema, pero hasta ahora nadie tiene una buena solución.
Sería bueno que la solución tuviera las siguientes características:
- puede interpretar más de un comando (por ejemplo,
command; command
) - puede interpretar redirecciones de flujo en las líneas (por ejemplo,
ls > /tmp/ls.txt
) - solo usa herramientas comunes de Linux
Puntos de bonificación si funciona en otros clones Unix sin requisitos demasiado exóticos.
Solución
¿Puedes convertir tu lista de comandos a un Makefile? Si es así, puede ejecutar " make -j X " ;.
Otros consejos
GNU Parallel http://www.gnu.org/software/parallel/ es una herramienta más general para paralelizar que PPSS.
Si el archivo de ejecución contiene:
command 1 > Logs/1.log
command 2 > Logs/2.log
command 3 > Logs/3.log
puedes hacer:
cat runfile | parallel -j+0
que ejecutará un comando por núcleo de CPU.
Si sus comandos son tan simples como los anteriores, ni siquiera necesita runfile pero puede hacer:
seq 1 3 | parallel -j+0 'command {} > Logs/{}.log'
Si tiene más computadoras disponibles para hacer el procesamiento, puede consultar las opciones --sshlogin y --trc para GNU Parallel.
Bien, después de publicar la pregunta aquí, encontré el siguiente proyecto que parece prometedor: ppss .
Editar: no es exactamente lo que quiero, PPSS se centra en el procesamiento de "todos los archivos en el directorio A".
Bueno, esta es una pregunta divertida de todos modos.
Esto es lo que haría, suponiendo bash (1) , por supuesto.
- calcula cuántos de estos comandos pueden ejecutarse de manera simultánea. No va a ser solo el número de núcleos; se suspenderán muchos comandos para E / S y ese tipo de cosas. Llame a ese número N.
N = 15
por ejemplo. - configura un controlador de señal de captura para la señal SIGCHLD, que ocurre cuando finaliza un proceso secundario.
trap signalHandler SIGCHLD
- coloca tu lista de comandos en una tubería
- escribe un bucle que lee stdin y ejecuta los comandos uno por uno, decrementando un contador. Cuando el contador es 0,
espera
s. - su controlador de señal, que se ejecuta en SIGCHLD, incrementa ese contador.
Entonces, ahora ejecuta los primeros comandos N
, luego espera. Cuando el primer hijo termina, la espera regresa, lee otra línea, ejecuta un nuevo comando y espera nuevamente.
Ahora, este es un caso que se ocupa de muchos trabajos que terminan juntos. sospecho que puedes salirte con una versión más simple:
N=15
COUNT=N
cat mycommands.sh |
while read cmd
do
eval $cmd &
if $((count-- == 0))
then
wait
fi
od
Ahora, este iniciará los primeros 15 comandos y luego ejecutará el resto de uno en uno a medida que finalice algún comando.
Una diversión similar de computación distribuida es la secuencia de comandos Mapreduce Bash:
http://blog.last.fm/2009/04 / 06 / mapreduce-bash-script
¡Y gracias por señalar ppss!
Puede usar el comando xargs , su --max-procs hace lo que quiere. Por ejemplo, la solución de Charlie Martin se convierte en xargs:
tr '\012' '\000' <mycommands.sh |xargs --null --max-procs=$X bash -c
detalles:
- X es el número de procesos máx. Por ejemplo: X = 15. --max-procs está haciendo la magia
- el primer tr está aquí para terminar líneas en bytes nulos para xargs - opción nula para que la redirección de comillas, etc. no se expanda incorrectamente
- bash -c ejecuta el comando
Lo probé con este archivo mycommands.sh, por ejemplo:
date
date "+%Y-%m-%d" >"The Date".txt
wc -c <'The Date'.txt >'The Count'.txt
Este es un caso específico, pero si está tratando de procesar un conjunto de archivos y producir otro conjunto de archivos de salida, puede iniciar #cores número de procesos y verificar si existe un archivo de salida antes de procesarlo. El siguiente ejemplo convierte un directorio de archivos .m4b en archivos .mp3:
Simplemente ejecute este comando tantas veces como tenga núcleos:
ls * m4b | mientras se lee f; hacer prueba -f $ {f% m4b} mp3 || mencoder -of rawaudio " $ f " -oac mp3lame -ovc copy -o $ {f% m4b} mp3; hecho & amp;
Puede ver la cola de mis tareas escrita en bash: https://github.com/pavelpat/yastq
Cola de tareas + Paralelo + Adición dinámica
Usando un FIFO, este script se bifurca para procesar la cola. De esta manera, puede agregar comandos a la cola sobre la marcha (cuando la cola ya está iniciada).
Uso: ./queue Command [# of children] [Queue name]
Ejemplo, con 1 hilo:
./queue "sleep 5; echo ONE" ./queue "echo TWO"
Salida:
ONE TWO
Ejemplo, con 2 hilos:
./queue "sleep 5; echo ONE" 2 ./queue "echo TWO"
Salida:
TWO ONE
Ejemplo, con 2 colas:
./queue "sleep 5; echo ONE queue1" 1 queue1 ./queue "sleep 3; echo ONE queue2" 1 queue2
Salida:
ONE queue2 ONE queue1
El script (guárdelo como " cola " y chmod + x cola):
#!/bin/bash #Print usage [[ $# -eq 0 ]] && echo Usage: <*> Command [# of children] [Queue name] && exit #Param 1 - Command to execute COMMAND="$1" #Param 2 - Number of childs in parallel MAXCHILD=1 [[ $# -gt 1 ]] && MAXCHILD="$2" #Param 3 - File to be used as FIFO FIFO="/tmp/defaultqueue" [[ $# -gt 2 ]] && FIFO="$3" #Number of seconds to keep the runner active when unused TIMEOUT=5 runner(){ #Associate file descriptor 3 to the FIFO exec 3"$FIFO" while read -u 3 -t $TIMEOUT line; do #max child check while [ `jobs | grep Running | wc -l` -ge "$MAXCHILD" ]; do sleep 1 done #exec in backgroud (eval "$line")& done rm $FIFO } writer(){ #fork if the runner is not running lsof $FIFO >/dev/null || (<*> "QueueRunner" "$MAXCHILD" "$FIFO" &) #send the command to the runner echo "$COMMAND" > $FIFO } #Create the FIFO file [[ -e "$FIFO" ]] || mkfifo "$FIFO" #Start the runner if in the runner fork, else put the command in the queue [[ "$COMMAND" == "QueueRunner" ]] && runner || writer