El módulo multiprocessing permite paralelismo real usando procesos separados, cada uno con su propio intérprete y sin compartir el GIL. Es la opción recomendada para cargas CPU-bound en máquinas con varios núcleos.
target, args y kwargs, similar a los hilos.start() lanza el proceso hijo; join() espera su finalización.Los primitivos son similares a threading pero se implementan con IPC (inter-proces communication).
queue.Queue es para hilos dentro del mismo proceso; multiprocessing.Queue usa IPC y funciona entre procesos.spawn (reimporta el módulo); en Linux puede usar fork, que es más rápido pero hereda estado. Protege tu código con if __name__ == "__main__":.Este ejemplo divide una lista grande en partes, reparte el trabajo entre procesos y compara el tiempo contra una versión con hilos. Se usa multiprocessing.Queue para recolectar resultados.
import math
import multiprocessing as mp
import time
from concurrent.futures import ThreadPoolExecutor
def es_primo(n: int) -> bool:
if n < 2:
return False
if n % 2 == 0:
return n == 2
limite = int(math.sqrt(n)) + 1
for i in range(3, limite, 2):
if n % i == 0:
return False
return True
def contar_primos(numeros, salida):
cuenta = sum(1 for n in numeros if es_primo(n))
salida.put(cuenta)
def dividir(lista, partes):
tam = len(lista)
paso = (tam + partes - 1) // partes
for i in range(0, tam, paso):
yield lista[i : i + paso]
def con_procesos(lista, workers):
salida = mp.Queue()
procesos = []
inicio = time.perf_counter()
for chunk in dividir(lista, workers):
p = mp.Process(target=contar_primos, args=(chunk, salida))
p.start()
procesos.append(p)
for p in procesos:
p.join()
total = sum(salida.get() for _ in procesos)
duracion = time.perf_counter() - inicio
return total, duracion
def con_hilos(lista, workers):
inicio = time.perf_counter()
with ThreadPoolExecutor(max_workers=workers) as pool:
resultados = list(pool.map(es_primo, lista))
total = sum(1 for r in resultados if r)
duracion = time.perf_counter() - inicio
return total, duracion
if __name__ == "__main__":
mp.freeze_support() # seguro para Windows
# Rango amplio para notar mejor la ventaja de procesos en CPU-bound
datos = list(range(1, 300_000))
workers = mp.cpu_count()
total_p, t_p = con_procesos(datos, workers)
total_h, t_h = con_hilos(datos, workers)
print(f"Procesos: {total_p} primos en {t_p:.2f}s con {workers} procesos")
print(f"Hilos: {total_h} primos en {t_h:.2f}s con {workers} hilos")
La versión con procesos suele ganar en cargas realmente pesadas porque cada proceso usa un núcleo sin compartir el GIL; pero si el trabajo por elemento es moderado o la lista es chica, el overhead de crear procesos y mover datos puede hacer que los hilos resulten más rápidos (como verás en algunas mediciones). Con un rango amplio como range(1, 300_000) o cálculos más costosos se aprecia mejor la ventaja de multiprocessing; también ayuda usar un Pool que reutilice procesos. Protege siempre el punto de entrada con if __name__ == "__main__": para evitar ejecuciones recursivas en Windows.