Ejecutar procesos en paralelo en Linux

Normalmente solo inicias una aplicación, o lanzas un solo proceso en el terminal. Por ejemplo si quieres convertir un audio a MP3, solo lanzarías ese proceso. Sin embargo, ¿que sucede si quieres convertir una decenea o centenar de audio?¿tienes que ir uno a uno?. No, esto sería desaprovechar tu ordenador. Puedes lanzar varios procesos en paralelo y no de forma secuencial. Así en este artículo te voy a comentar diferentes opciones que tienes disponibles para ejecutar procesos en paralelo en Linux.

No es que esto de lanzar procesos en paralelo en Linux sea muy complicado. Mas bien todo lo contrario. Sin embargo, en muchas ocasiones no lo hago, porque no lo tengo en la cabeza. Simplemente, no caigo en que es una posibilidad de ejecutar procesos y terminar cuanto antes. Pero como digo, no solo tienes una posibilidad de ejecutar procesos en paralelo en Linux, sino que tienes varias, como podrás leer en este artículo.

Ejecutar procesos en paralelo en Linux

Ejecutar procesos en paralelo en Linux

La tarea

Con el fin de que este artículo sea lo mas comprensible posible, he implementado un sencillo script, que es el que servirá para que sigas la explicación con detalle. Este script lo único que hace es esperar un tiempo y posteriormente imprimir un texto con la duración de la tarea. El script se llama tarea.sh.

$ cat tarea.sh 

#!/bin/bash
inicio=$(date +%s%3N)
sleep $1
fin=$(date +%s%3N)
tiempo=$(echo "scale=3;($fin-$inicio)/1000.0" | bc)
echo "$2 en $tiempo segundos"

Como ves la tarea es sencilla, y en el mensaje que imprime aparece el tiempo real que ha durado la tarea. Lo único extraño que puedes encontrar es que aparece la operación con registro hasta milisegundos. Esto es simplemente, porque quería comprobar la precisión de sleep. Sin embargo, no tengo muy claro que esto que he hecho tenga alguna utilidad. De cualquier forma ahí queda.

Así para ejecutar la tarea simplemene tienes que lanzar la siguiente instrucción time ./tarea.sh 1 tarea1. Esto te devolverá lo siguiente,

$ tarea.sh 1 tarea1

tarea1 en 1.003 segundos

Yo además lo ejecuto como time ./tarea.sh 1 tarea1 para que me muestre los resultados de tiempo. Así,

$ time ./tarea.sh 1 tarea1

tarea1 en 1.003 segundos

real 0m1,012s
user 0m0,009s
sys  0m0,004s

Ejecución secuencial

Tal y como te he indicado en la introducción, es posible que el primer pensamiento que tengas es lanzar todas las tareas de forma secuencial, una detrás de otra. Para ello, te introduzco el script que seguro tienes en mente.

Le he llamado secuencial.sh y tiene cuatro tareas nombradas como Tarea1 a Tarea4. Sin embargo los tiempos los he asignado en sentido inverso. Es decir a la primera tarea 4 segundos, a la segunda tarea 3 segundos y así sucesivamente. Esto lo he hecho así, por lo que podrás ver mas tarde,

$ cat secuencial.sh

#!/bin/bash
./tarea.sh 4 Tarea1
./tarea.sh 3 Tarea2
./tarea.sh 2 Tarea3
./tarea.sh 1 Tarea4

Si ejecutas este proceso conforme te he indicado en el apartado anterior tendrás el siguiente resultado,

$ time ./secuencial.sh 

Tarea1 en 4.006 segundos
Tarea2 en 3.003 segundos
Tarea3 en 2.003 segundos
Tarea4 en 1.004 segundos

real 0m10,055s
user 0m0,033s
sys  0m0,017s

Como ya te podías imaginar el proceso ha tardado unos 10 segundos mas esas milésimas que seguro que no has podido apreciar.

Ejecución en paralelo

Evidentemente ejecutar todos estos procesos de esta manera es una forma de desaprovechar tu ordenador que no tiene nombre. Es necesario hacerlo de una forma mas eficiente. Así, si leíste el artículo sobre procesos en segundo plano, seguro que ya tienes en mente lo que hay que hacer. De nuevo he implementado un script, donde los procesos se ejecutan en segundo plano. Así, el script tiene el siguiente aspecto,

$ cat paralelo.sh

#!/bin/bash
./tarea.sh 4 Tarea1 &
./tarea.sh 3 Tarea2 &
./tarea.sh 2 Tarea3 &
./tarea.sh 1 Tarea4 &
wait

La última instrucción wait te permitirá que el proceso principal termine cuando hayan terminado el resto de procesos. Así es mucho mas fácil comprobar los tiempos de ejecución.

Si ahora lo ejecutas conforme a lo que has visto en apartados anteriores, el resultado es el siguiente,

$ time ./paralelo.sh 

Tarea4 en 1.004 segundos
Tarea3 en 2.005 segundos
Tarea2 en 3.005 segundos
Tarea1 en 4.004 segundos

real 0m4,014s
user 0m0,030s
sys  0m0,018s

Lo primero que observas es que la salida es bastante caótica. Al lanzarse las tareas en segundo plano y ejecutarse de forma simultánea, han ido terminando según su tiempo de proceso. Así, como era de esperar la Tarea4 es la primera que ha terminado, porque su duración es la menor.

De esta manera el tiempo empleado para ejecutar el script completo se corresponde con el tiempo empleado para ejecutar la tarea de mas duración. Básicamente has pasado de tardar 10 segundos a tardar 4 segundos. La diferencia es mas que considerable.

Ejecución en paralelo por lotes

Es posible que no quieras que todos los procesos se ejecuten de forma simultánea, sino que se ejecuten por lotes. Así puedes querer que se ejecuten por lotes de dos ó de cuatro ó de la cantidad que consideres. En este caso, solo tienes que modificar ligeramente el script anterior para conseguir el objetivo que buscas. Así, tu nuevo script quedará con un aspecto como el que ves a continuación, en el caso de que te hayas decidido por bloques de dos procesos,

$ cat paralelo_lotes.sh 

#!/bin/bash
./tarea.sh 4 Tarea1 &
./tarea.sh 3 Tarea2 &
wait
./tarea.sh 2 Tarea3 &
./tarea.sh 1 Tarea4 &
wait

Si pasas a ejecutarlo el resultado tendrá un aspecto como el que puedes ver a continuación,

$ time ./paralelo_lotes.sh 

Tarea2 en 3.003 segundos
Tarea1 en 4.004 segundos
Tarea4 en 1.006 segundos
Tarea3 en 2.006 segundos

real 0m6,032s
user 0m0,034s
sys  0m0,016s

De nuevo la ejecución es caótica, terminando el primero el proceso mas corto. Eso si, el proceso mas corto de cada uno de los lotes.

Un script agnóstico…

Este script de lanzar tareas está bien… Sin embargo, estaría mejor si no tuvieras que personalizarlo tanto. Es decir, si no tuvieras que crear uno cada vez que tienes que lanzar procesos en paralelo. Esto no es nada complicado. El script tendría un aspecto como el que puedes ver a continuación,

$ cat super_paralelo.sh 

#!/bin/bash
for tarea in "$@"
do
    $tarea &
done
wait

Muy sencillo, a la vez que tremendamente efectivo. Pero, ¿como ejecutarías cuatro tareas como estás haciendo hasta el momento?. Tan sencillo como ejecutar lo siguiente,

$ time ./super_paralelo.sh "./tarea.sh 4 tarea1" "./tarea.sh 3 tarea2" "./tarea.sh 2 tarea3" "./tarea.sh 1 tarea4"

tarea4 en 1.003 segundos
tarea3 en 2.005 segundos
tarea2 en 3.005 segundos
tarea1 en 4.005 segundos

real    0m4,018s
user    0m0,035s
sys 0m0,012s

xargs

Otra opción de lanzar procesos en paralelo es utilizando xargs. Y realmente es muy pero que muy sencillo y limpio. Es posible que mas sencillo de lo que hemos visto hasta el momento. Así para el caso que te ocupa, podrías ejecutarlo de la siguiente forma,

time echo 4 Tarea1 3 Tarea2 2 Tarea3 1 Tarea4 | xargs -n 2 -P 4 ./tarea.sh

Y el resultado sería,

Tarea4 en 1.003 segundos
Tarea3 en 2.007 segundos
Tarea2 en 3.004 segundos
Tarea1 en 4.006 segundos

real    0m4,015s
user    0m0,033s
sys 0m0,012s

Respecto a las opciones que he tomado, indicarte que,

  • -n 2 indica los argumentos como debe pasarlos a cada tarea. Así, con -n 2, le estoy indicando que los tiene que agrupar de dos en dos.
  • -P 4 es para establecer el número de procesos que va a lanzar de forma simultánea. Así, si pones -P 2 los agrupará de dos en dos, y de esta forma el tiempo ronda los cinco segundos. Y, evidentemente si pones -P 1, los ejecutará de uno a uno, y el resultado de hacerlo así ya lo conoces… 10 segundos.

parallel

Por último, indicarte que existe una herramienta especialmente pensada para esto. Eso si no se encuentra instalada por defecto. Sin embargo, instalarla es cuestión sencilla. Simplemente sudo apt install parallel.

Una vez instalada parallel para repetir el proceso que has estado haciendo hasta el momento, tan solo tienes que ejecutar la siguiente instrucción,

time parallel -j 5 --link ./tarea.sh ::: 4 3 2 1 ::: Tarea1 Tarea2 Tarea3 Tarea4

El resultado es el siguiente,

Tarea4 en 1.003 segundos
Tarea3 en 2.003 segundos
Tarea2 en 3.005 segundos
Tarea1 en 4.005 segundos

real    0m4,218s
user    0m0,219s
sys 0m0,066s

Dos observaciones. La opción -j 5 indica el número máximo de procesos en paralelo. En este caso he puesto 5 pero evidentemente, mas de cuatro no puede haber. De esta forma si pones -j 2 se ejecutarán en lotes de dos. Con esto, el tiempo que se ha producido es de 5 segundos. Evidentemente si pones -j 1, se ejecutarán de uno en uno, con lo que el tiempo, como te puedes imaginar se irá a los diez segundos.

Por otro lado la opción --link lo que hace es combinar un argumento del primer grupo con un argumento del segundo. Si no lo haces de este modo, lo que combina es todos con todos, y esto no es lo que andas buscando.

Conclusión

Con esto le has dado un repaso a diferentes formas de lanzar procesos en paralelo o de forma secuencial. Esto ya depende de ti. Elige lo que mas te conviene, o lo que te resulte más cómodo. Pero sobre todo, elige lo que sea mas sencillo, para que tus scripts resulten fáciles de leer.


Más información,

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *