Pregunta Paralelamente el script Bash con la cantidad máxima de procesos


Digamos que tengo un bucle en Bash:

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

do-something está atado a la CPU y tengo un bonito y brillante procesador de 4 núcleos. Me gustaría poder correr hasta 4 do-somethinges a la vez.

El enfoque ingenuo parece ser:

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

Esto correrá todas  do-somethings a la vez, pero hay un par de inconvenientes, principalmente que do-something también puede tener algunas E / S significativas que realizan todas de inmediato podría disminuir la velocidad un poco. El otro problema es que este bloque de código vuelve inmediatamente, así que no hay forma de hacer otro trabajo cuando todo el do-somethings han terminado.

¿Cómo escribirías este ciclo para que siempre haya X do-somethings corriendo a la vez?


76
2017-09-01 16:47


origen


Respuestas:


Dependiendo de lo que quiera 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.

52
2018-05-19 07:50



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

some-command | parallel do-something

GNU Parallel también admite ejecutar trabajos en computadoras remotas. Esto ejecutará uno por núcleo de CPU en las computadoras remotas, incluso si tienen diferente número 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 sea procesado y transferido a la computadora dada. Cuando finalice my_script, queremos que foo.out se transfiera nuevamente y luego queremos que foo.jpeg y foo.out sean eliminados 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 el resultado como entrada para otro programa:

some-command | parallel do-something | postprocess

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


35
2018-06-10 01:37



maxjobs = 4
parallelize () {
        while [$ # -gt 0]; hacer
                jobcnt = (`trabajos -p`)
                if [$ {# jobcnt [@]} -lt $ maxjobs]; entonces
                        hacer algo $ 1 y
                        cambio
                más
                        dormir 1
                fi
        hecho
        Espere
}

paralelizar arg1 arg2 "5 args a tercer trabajo" arg4 ...

22
2017-09-01 18:00



En lugar de un simple bash, use un Makefile, luego especifique el número de trabajos simultáneos con make -jX donde X es la cantidad de trabajos para ejecutar a la vez.

O puedes usar wait ("man wait"): inicia varios procesos secundarios, llama wait - Saldrá cuando termine el proceso hijo.

maxjobs = 10

foreach line in `cat file.txt` {
 jobsrunning = 0
 while jobsrunning < maxjobs {
  do job &
  jobsrunning += 1
 }
wait
}

job ( ){
...
}

Si necesita almacenar el resultado del trabajo, asigne su resultado a una variable. Después wait simplemente verifica qué contiene la variable.


11
2017-09-01 16:50



Tal vez intente una utilidad de paralelización en lugar de reescribir el bucle? Soy un gran fan de xjobs. Uso xjobs todo el tiempo para copiar archivos en masa a través de nuestra red, generalmente al configurar un nuevo servidor de base de datos. http://www.maier-komor.de/xjobs.html


8
2017-09-01 16:55



Aquí hay una solución alternativa que puede insertarse en .bashrc y usarse para un trazador de líneas diario:

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

Para usarlo, todo lo que uno tiene que hacer es poner & después de los trabajos y una llamada pwait, el parámetro da la cantidad de procesos paralelos:

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

Sería más agradable de usar wait en lugar de esperar ocupado en la salida de jobs -p, pero no parece haber una solución obvia para esperar hasta que se termine cualquiera de los trabajos dados en lugar de todos.


8
2018-05-19 03:40



Al hacer esto bien en bash es probablemente imposible, puedes hacer un semi-derecha con bastante facilidad. bstark dio una buena aproximación de la derecha, pero la suya tiene los siguientes defectos:

  • División de palabras: no puede pasarle ningún trabajo que use ninguno de los siguientes caracteres en sus argumentos: espacios, pestañas, líneas nuevas, estrellas, signos de interrogación. Si lo haces, las cosas se romperán, posiblemente de forma inesperada.
  • Se basa en el resto de su secuencia de comandos para no hacer un fondo de nada. Si lo hace, o más tarde agrega algo a la secuencia de comandos que se envía en segundo plano porque se olvidó de que no le permitieron usar trabajos con antecedentes debido a su fragmento, las cosas se romperán.

Otra aproximación que no tiene estos defectos es la siguiente:

scheduleAll() {
    local job i=0 max=4 pids=()

    for job; do
        (( ++i % max == 0 )) && {
            wait "${pids[@]}"
            pids=()
        }

        bash -c "$job" & pids+=("$!")
    done

    wait "${pids[@]}"
}

Tenga en cuenta que este es fácilmente adaptable y también verifica el código de salida de cada trabajo, ya que termina para que pueda advertir al usuario si un trabajo falla o establecer un código de salida para scheduleAll de acuerdo con la cantidad de trabajos que fallaron, o algo así.

El problema con este código es solo eso:

  • Programa cuatro trabajos (en este caso) a la vez y luego espera que finalicen los cuatro. Algunos se pueden hacer antes que otros, lo que hará que el siguiente lote de cuatro trabajos espere hasta que se complete el lote más largo del lote anterior.

Una solución que se ocupe de este último problema debería usar kill -0 para sondear si alguno de los procesos ha desaparecido en lugar de wait y programar el próximo trabajo. Sin embargo, eso introduce un pequeño problema nuevo: tiene una condición de carrera entre el final del trabajo y el kill -0 verificando si terminó Si el trabajo finalizó y otro proceso en su sistema se inicia al mismo tiempo, tomando un PID aleatorio que resulta ser el del trabajo que acaba de finalizar, el kill -0 no notará que su trabajo ha terminado y las cosas se romperán nuevamente.

Una solución perfecta no es posible en bash.


6
2018-05-19 07:26



Si estás familiarizado con el make comando, la mayoría de las veces puede expresar la lista de comandos que desea ejecutar como un archivo MAKE. Por ejemplo, si necesita ejecutar $ SOME_COMMAND en archivos * .input cada uno de los cuales produce * .output, puede usar el archivo make

INPUT = a.input b.input
SALIDA = $ (ENTRADA: .input = .output)

%.salida entrada
    $ (SOME_COMMAND) $ <$ @

todo: $ (SALIDA)

y luego solo corre

hacer -j <NÚMERO>

para ejecutar como máximo NUMBER comandos en paralelo.


5
2018-05-21 20:33



función para bash:

parallel ()
{
    awk "BEGIN{print \"all: ALL_TARGETS\\n\"}{print \"TARGET_\"NR\":\\n\\t@-\"\$0\"\\n\"}END{printf \"ALL_TARGETS:\";for(i=1;i<=NR;i++){printf \" TARGET_%d\",i};print\"\\n\"}" | make $@ -f - all
}

utilizando:

cat my_commands | parallel -j 4

3
2018-02-22 10:14