Cómo correr funciones de manera paralelas en Python

Desde hace un tiempo, que por las noches o un ratito los fin de semanas, busco expandir un poco mi conocimiento de Python y lo hago a través de proyectos.

No pruebo cosas aisladas, sino que trato de armar un proyecto, como por ejemplo, EZCompose y una vez que tengo algo medio funcional, busco maneras de mejorarlo, que sea más eficiente, performante y que tenga más features, de esta manera, busco aprender cada vez más.

GitHub - xe-nvdk/ezcompose: EZCompose is a docker-compose.yml builder. Define your images, networks, volumes, ports and more, easily.
EZCompose is a docker-compose.yml builder. Define your images, networks, volumes, ports and more, easily. - GitHub - xe-nvdk/ezcompose: EZCompose is a docker-compose.yml builder. Define your images...

Empecemos

El proyecto que estoy armando ahora, y que no he liberado aún, porque quiero llegar a tener una version lo más completa y funcional posible, se llama Vibora. Vibora es un software que te va a ayudar a hacer chequeos para ver si determinado host en determinado puerto esta online, así como también medir el tiempo de respuesta del mismo.

Ahora bien, pensando en que en la base de datos puedo tener una gran cantidad de hosts, puertos y tiempos de respuesta que chequear cada minuto, no puedo esperar que haga chequeo de manera secuencial, sino, se tardaría mucho y no podría "cumplir" con los intervalos de chequeo. ¿Entonces?

La manera que encontre de resolverlo es usando el modulo multiprocessing, especificamente el submodulo Process y que básicamente, lo que me permite es correr en paralelo, diferentes funciones.

Veamos un ejemplo, tenemos estas dos funciones de Python con un loop, si lo ejecutamos de la siguiente manera, la funcion func2 no va a arrancar nunca, porque func1 no va a terminar nunca, justamente, es un loop.

import random
import datetime

def func1():
    while True:
        random1 = random.randint(1, 100)
        print("Esta es random1", random1, datetime.datetime.now())

def func2():
    while True:
        random2 = random.randint(1, 100)
        print("Esta es random2", random2, datetime.datetime.now())

Si lo ejecutamos tal cual esta, el output de esto va a ser algo así:

Esta es random1 33 2022-07-19 10:08:35.043138
Esta es random1 66 2022-07-19 10:08:35.043141
Esta es random1 63 2022-07-19 10:08:35.043143
Esta es random1 8 2022-07-19 10:08:35.043145
Esta es random1 79 2022-07-19 10:08:35.043147
Esta es random1 30 2022-07-19 10:08:35.043149
Esta es random1 21 2022-07-19 10:08:35.043152
Esta es random1 37 2022-07-19 10:08:35.043154

Se fijan, random2, no aparece, por el motivo que comente más arriba, hasta que ese loop no "termine" func2 no va a arrancar.

Entonces, como hacemos para que los dos loops corran de forma paralela? bueno, para eso vamos a usar el modulo multiprocesing. El código se vería de la siguiente manera:

import random
from multiprocessing import Process
import datetime

def func1():
    while True:
        random1 = random.randint(1, 100)
        print("Esta es random1", random1, datetime.datetime.now())

def func2():
    while True:
        random2 = random.randint(1, 100)
        print("Esta es random2", random2, datetime.datetime.now())

if __name__ == '__main__':
    p1 = Process(target=func1)
    p2 = Process(target=func2)
    p1.start()
    p2.start()

El ouput se ve de la siguiente manera:

Esta es random2 25 2022-07-19 10:14:46.115486
Esta es random1 41 2022-07-19 10:14:46.115489
Esta es random2 25 2022-07-19 10:14:46.115489
Esta es random1 65 2022-07-19 10:14:46.115492
Esta es random2 19 2022-07-19 10:14:46.115492
Esta es random1 90 2022-07-19 10:14:46.115494
Esta es random2 61 2022-07-19 10:14:46.115494
Esta es random1 95 2022-07-19 10:14:46.115497
Esta es random2 21 2022-07-19 10:14:46.115497
Esta es random1 13 2022-07-19 10:14:46.115499
Esta es random2 86 2022-07-19 10:14:46.115501

Para ir cerrando

Como pueden ver no es para nada dificil, ya que este es un ejemplo muy sencillo de cómo correr funciones de manera paralela en Python.

Otras opciones serían usar thread, aunque hay algunas diferencias, que ya veremos más adelante.