1 usuario conectado

Concurrencia y Paralelismo en Python: Desatando el Paralelismo Real con multiprocessing

Comparte esto

Concurrencia y Paralelismo en Python: Desatando el Paralelismo Real con multiprocessing

Compra libros de Python en Amazon al mejor precio

Tras explorar el módulo threading y su papel en la concurrencia en Python, es momento de adentrarnos en el reino del paralelismo real a través del módulo multiprocessing. Como vimos, la limitación del Global Interpreter Lock (GIL) restringe la capacidad de los threads para aprovechar múltiples núcleos de CPU en tareas intensivas. El módulo multiprocessing surge como una solución poderosa para superar esta barrera, permitiendo la ejecución simultánea de código Python en procesos separados.

¿Por qué usar Procesos (multiprocessing) en Python?

El módulo multiprocessing permite crear y gestionar procesos, que son unidades de ejecución independientes con su propio espacio de memoria y su propio intérprete de Python. Al operar en procesos separados, se elude la restricción del GIL, posibilitando el verdadero paralelismo en sistemas multinúcleo. Esto se traduce en una mejora significativa del rendimiento para tareas que pueden dividirse en subtareas independientes y que son principalmente dependientes de la potencia de la CPU.

Algunos casos de uso ideales para multiprocessing incluyen:

Creando y Gestionando Procesos con multiprocessing

La interfaz del módulo multiprocessing es sorprendentemente similar a la del módulo threading, lo que facilita la transición entre ambos. La clase principal es Process, que representa un proceso separado.

Python
 
import multiprocessing
import time

def tarea_pesada(nombre):
    print(f"Proceso {nombre}: Iniciando")
    resultado = 0
    for i in range(10**7):  # Simula un cálculo intensivo
        resultado += i
    print(f"Proceso {nombre}: Finalizando con resultado {resultado}")

if __name__ == "__main__":
    # Crear dos objetos Process
    proceso1 = multiprocessing.Process(target=tarea_pesada, args=("Uno",))
    proceso2 = multiprocessing.Process(target=tarea_pesada, args=("Dos",))

    # Iniciar los procesos
    proceso1.start()
    proceso2.start()

    print("Programa principal continuando...")

    # Esperar a que los procesos finalicen su ejecución
    proceso1.join()
    proceso2.join()

    print("Todos los procesos han terminado.")

En este ejemplo:

  1. Definimos una función tarea_pesada que simula un cálculo intensivo.
  2. Creamos dos instancias de la clase Process, especificando la función target y sus args.
  3. Llamamos a start() para iniciar la ejecución de cada proceso. Estos procesos se ejecutan ahora en paralelo (si hay núcleos de CPU disponibles).
  4. Utilizamos join() para esperar a que cada proceso termine antes de que el programa principal continúe.

Comunicación entre Procesos

Dado que los procesos tienen espacios de memoria separados, la comunicación entre ellos es más compleja que entre threads. El módulo multiprocessing proporciona varias herramientas para facilitar esta comunicación:

Ejemplo de Comunicación con Queue:

Python
 
import multiprocessing
import time

def productor(cola):
    for i in range(5):
        time.sleep(1)
        item = f"Producto {i}"
        print(f"Productor: Encolando {item}")
        cola.put(item)

def consumidor(cola):
    while True:
        item = cola.get()
        print(f"Consumidor: Recibido {item}")
        if item is None:
            break
        time.sleep(2)  # Simula procesamiento

if __name__ == "__main__":
    cola_compartida = multiprocessing.Queue()

    p = multiprocessing.Process(target=productor, args=(cola_compartida,))
    c = multiprocessing.Process(target=consumidor, args=(cola_compartida,))

    p.start()
    c.start()

    p.join()
    cola_compartida.put(None)  # Señal para que el consumidor termine
    c.join()

    print("Productor y consumidor han terminado.")

En este ejemplo, un proceso productor encola elementos en una Queue, y un proceso consumidor los desencola y procesa. La Queue maneja la sincronización necesaria para la comunicación segura entre los procesos.

Consideraciones al Usar multiprocessing

Conclusión

El módulo multiprocessing en Python es la clave para desbloquear el verdadero paralelismo en tareas intensivas en CPU. Al crear procesos separados que eluden la limitación del GIL, permite aprovechar al máximo los sistemas multinúcleo, lo que resulta en mejoras significativas en el rendimiento. Si bien la gestión de procesos y la comunicación entre ellos son más complejas que con threads, para las cargas de trabajo adecuadas, los beneficios del paralelismo real superan con creces estas consideraciones. La elección entre threading y multiprocessing dependerá de la naturaleza de las tareas: threading para concurrencia en E/S y multiprocessing para paralelismo en CPU. Dominar ambos módulos te equipa con las herramientas necesarias para abordar una amplia gama de desafíos de programación concurrente y paralela en Python.