multiprocessing
multiprocessing inicia procesos hijos con intérpretes de Python separados: la forma portable de paralelizar trabajo limitado por CPU en compilaciones de Python con GIL por defecto. Comparte datos a través de colas, tuberías o multiprocessing.shared_memory.
Receta
from multiprocessing import Pool
def work(n: int) -> int:
return n * n
if __name__ == "__main__":
with Pool() as pool:
print(pool.map(work, range(5)))Cuándo usar esto:
- Bucles de Python puro con uso intensivo de CPU
- Transformaciones de datos paralelas entre núcleos
- Aislamiento de dominios de fallo (el fallo de un trabajador no mata a todos)
- Evitar el GIL para pipelines de cómputo
- Trabajos por lotes con tareas independientes
Ejemplo de funcionamiento
from multiprocessing import Process, Queue
def producer(q: Queue, count: int) -> None:
for i in range(count):
q.put(i * i)
def consumer(q: Queue) -> None:
items = []
while True:
item = q.get()
if item is None:
break
items.append(item)
print("suma", sum(items))
if __name__ == "__main__":
q: Queue = Queue()
p = Process(target=producer, args=(q, 5))
c = Process(target=consumer, args=(q,))
c.start()
p.start()
p.join()
q.put(None)
c.join()Lo que esto demuestra:
Queuepasa objetos "picklable" entre procesos- El centinela
Nonetermina el bucle del consumidor if __name__ == "__main__"es necesario para el método de inicio "spawn" (macOS, Windows)- Los procesos no comparten objetos en memoria por defecto
Análisis detallado
Métodos de inicio
| Método | Comportamiento |
|---|---|
| spawn | Intérprete nuevo (por defecto en macOS/Win) |
| fork | Copia del padre (Unix, cuidado con los hilos) |
Opciones de IPC (Comunicación entre Procesos)
Queue,Pipepara mensajesshared_memorypara arrays grandes (3.8+)- Preferir pasar instantáneas inmutables sobre la mutación compartida
Errores comunes
- Falta el guardián
main- "spawn" recursivo al importar. Solución:if __name__ == "__main__". - Cierres (closures) no "picklable" - el objetivo del trabajador debe ser de nivel superior o "picklable". Solución:
initializer+ funciones de módulo. - "Pickling" de objetos enormes - IPC lento. Solución: memoria compartida o fragmentos respaldados por archivos.
fork+ hilos - posibles interbloqueos (deadlocks). Solución: preferir "spawn" en macOS; iniciar procesos antes que hilos.- Sobresuscripción de
Pool- memoria × trabajadores agota la RAM. Solución:maxtasksperchildy límite de trabajadores.
Alternativas
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
| concurrent.futures.ProcessPoolExecutor | API más simple | Necesitas primitivas de Process |
| subprocess (un solo uso) | Comandos de shell | Granja de funciones de Python |
| Extensión C / NumPy | Libera el GIL en hilos | Bucle "hot" de Python puro |
Preguntas frecuentes
¿Tamaño del Pool?
Punto de partida os.cpu_count(); menor si cada tarea asigna mucha memoria.
¿Por qué "spawn" es el valor por defecto en macOS?
Más seguro con frameworks y el runtime de Objective-C que fork después de iniciar hilos.
¿Pueden los procesos compartir un `dict`?
Usa Manager().dict() - más lento; prefiere el paso de mensajes.
¿Cómo registrar desde los trabajadores?
Configura el logging en el initializer del trabajador; evita manejadores obsoletos heredados.
¿Módulo `Process` vs `subprocess`?
multiprocessing ejecuta funciones de Python; subprocess ejecuta ejecutables.
¿`maxtasksperchild`?
Recicla trabajadores después de N tareas para frenar fugas de memoria en pools largos.
¿Cómo cancelo el trabajo?
shutdown(cancel_futures=True) del Executor (3.9+) o centinela a través de Queue.
¿Son "picklable" las lambdas?
No con "spawn" - usa def a nivel de módulo.
¿`asyncio` reemplaza a `multiprocessing`?
No - asyncio para I/O concurrente; multiprocessing para CPU paralela.
¿Cómo hacer benchmarking?
Tiempo de pared (wall time) con tamaño de carga realista; incluye la sobrecarga de "spawn" en tareas cortas.
Relacionado
- concurrent.futures - ProcessPoolExecutor
- El GIL y Python con Hilos Libres - por qué procesos
- Colas y Productor/Consumidor - patrones
- subprocess - comandos externos
Versiones de la pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.