El módulo multiprocessing en Python ofrece un potente conjunto de herramientas para crear programas paralelos que aprovechan eficientemente los procesadores multinúcleo. A diferencia del multihilo estándar, limitado por el Global Interpreter Lock (GIL), multiprocessing permite ejecutar varios procesos simultáneamente, cada uno con su propia memoria y ejecución independiente.
Principios fundamentales de multiprocessing
El multiprocesamiento en Python se basa en la creación de procesos independientes que pueden ejecutarse en paralelo en diferentes núcleos del procesador. Cada proceso opera en un espacio de memoria aislado, eliminando los problemas de datos compartidos típicos del multihilo.
Los componentes clave del módulo multiprocessing incluyen:
- Process — clase para crear y gestionar procesos individuales
- Pool — grupo de procesos para ejecutar múltiples tareas de forma eficiente
- Queue — colas para transferir datos de forma segura entre procesos
- Lock — mecanismos de sincronización para controlar el acceso a recursos
- Manager — objetos para gestionar datos compartidos
¿Cuándo usar multiprocessing?
Multiprocessing es más efectivo en los siguientes casos:
Tareas intensivas en CPU: cálculos matemáticos, procesamiento de imágenes, análisis de datos, simulaciones científicas. Estas tareas se benefician enormemente de la ejecución paralela en múltiples núcleos.
Procesamiento de grandes volúmenes de datos: cuando necesitas aplicar la misma función a muchos elementos, multiprocessing puede acelerar significativamente la ejecución.
Tareas independientes: operaciones que no requieren intercambio frecuente de datos entre procesos.
Para tareas intensivas en E/S (archivos, solicitudes de red), es mejor usar programación asíncrona (asyncio) o multihilo.
Creación y gestión de procesos
Ejemplo básico con la clase Process
import multiprocessing
import time
def tarea_trabajador(nombre, duracion):
"""Función para ejecutar en un proceso separado"""
print(f"Proceso {nombre} iniciado")
time.sleep(duracion)
print(f"Proceso {nombre} finalizado")
if __name__ == "__main__":
# Creación de procesos
proceso1 = multiprocessing.Process(target=tarea_trabajador, args=("Worker-1", 3))
proceso2 = multiprocessing.Process(target=tarea_trabajador, args=("Worker-2", 2))
# Inicio de procesos
tiempo_inicio = time.time()
proceso1.start()
proceso2.start()
# Esperar a que terminen
proceso1.join()
proceso2.join()
tiempo_fin = time.time()
print(f"Tiempo total de ejecución: {tiempo_fin - tiempo_inicio:.2f} segundos")
Herencia de la clase Process
import multiprocessing
import os
import time
class ProcesoPersonalizado(multiprocessing.Process):
def __init__(self, nombre, cantidad_trabajo):
super().__init__()
self.nombre = nombre
self.cantidad_trabajo = cantidad_trabajo
def run(self):
"""Método que se ejecuta en el proceso"""
print(f"Proceso {self.nombre} (PID: {os.getpid()}) iniciado")
for i in range(self.cantidad_trabajo):
print(f"{self.nombre}: ejecutando tarea {i+1}")
time.sleep(1)
print(f"Proceso {self.nombre} finalizado")
if __name__ == "__main__":
procesos = []
# Crear varios procesos
for i in range(3):
p = ProcesoPersonalizado(f"Proceso-{i+1}", 3)
procesos.append(p)
p.start()
# Esperar a que todos terminen
for p in procesos:
p.join()
Trabajo con grupos de procesos (Pool)
Pool proporciona una interfaz conveniente para gestionar múltiples tareas de forma eficiente. Con Pool puedes distribuir automáticamente el trabajo entre varios procesos, simplificando el código y optimizando el rendimiento.