Multiprocessing en Python: usa varios núcleos correctamente

Publicado el: 11/07/2026
Tempo de leitura: 4 minutos
Uso de multiprocessing em Python para acelerar scripts

El módulo multiprocessing permite ejecutar trabajo en procesos separados y aprovechar varios núcleos de CPU. A diferencia de los hilos, cada proceso tiene su propio intérprete y su propia memoria, por lo que puede ejecutar código Python en paralelo para tareas intensivas como cálculos, compresión, procesamiento de imágenes o transformaciones numéricas. El beneficio no es automático: crear procesos y transferir datos también tiene un coste.

Antes de elegir esta herramienta, compara el enfoque con threading en Python, comprende el GIL, revisa asyncio para tareas de espera y mide resultados con timeit.

Cuándo usar multiprocessing

Es una buena opción cuando la tarea consume CPU y puede dividirse en partes relativamente independientes. Ejemplos: calcular hashes de muchos archivos, aplicar filtros a imágenes, analizar documentos o ejecutar simulaciones. Para descargar páginas, esperar respuestas de APIs o leer sockets, los hilos o asyncio suelen consumir menos recursos.

Crear un proceso básico

from multiprocessing import Process
import os


def trabajar(nombre):
    print(f"Proceso {nombre}, PID {os.getpid()}")


if __name__ == "__main__":
    proceso = Process(target=trabajar, args=("A",))
    proceso.start()
    proceso.join()
    print("Proceso finalizado")

start() inicia el proceso y join() espera a que termine. El bloque if __name__ == "__main__" es esencial, especialmente en Windows y macOS, para evitar que cada proceso vuelva a crear otros procesos de forma recursiva.

Ejecutar varios procesos

from multiprocessing import Process


def cuadrado(numero):
    print(numero, numero ** 2)


if __name__ == "__main__":
    procesos = []

    for numero in range(4):
        proceso = Process(target=cuadrado, args=(numero,))
        proceso.start()
        procesos.append(proceso)

    for proceso in procesos:
        proceso.join()

El orden de salida no está garantizado. Los procesos avanzan según la planificación del sistema operativo.

Usar Pool para muchas tareas

Pool mantiene un conjunto de trabajadores y distribuye elementos:

from multiprocessing import Pool


def calcular(numero):
    return numero ** 2


if __name__ == "__main__":
    numeros = list(range(10))

    with Pool() as pool:
        resultados = pool.map(calcular, numeros)

    print(resultados)

map() conserva el orden de entrada. El gestor de contexto cierra y espera a los trabajadores correctamente.

Elegir el número de procesos

Puedes consultar los núcleos lógicos:

import os

print(os.cpu_count())

Usar más procesos que núcleos no siempre acelera el programa. Para trabajo intensivo de CPU, comienza con un número cercano a los núcleos disponibles. Deja margen si el equipo debe seguir atendiendo otras tareas.

Procesar resultados a medida que llegan

imap_unordered() entrega resultados sin esperar el orden original:

from multiprocessing import Pool


def pesado(numero):
    return numero, numero ** 3


if __name__ == "__main__":
    with Pool(processes=4) as pool:
        for resultado in pool.imap_unordered(pesado, range(20)):
            print(resultado)

Esto reduce la espera cuando las tareas tienen duraciones diferentes y el orden no importa.

El tamaño de chunks

Enviar cada elemento individualmente aumenta la comunicación. map() e imap() aceptan chunksize:

resultados = pool.map(calcular, numeros, chunksize=100)

Un bloque mayor reduce sobrecarga, pero puede distribuir peor trabajos desiguales. Prueba con datos representativos.

Los datos deben poder serializarse

Los argumentos y resultados suelen pasar por pickle. Funciones locales, lambdas y algunos objetos abiertos no pueden serializarse fácilmente:

# Mejor: función en el nivel superior del módulo
def transformar(registro):
    return registro["valor"] * 2

Evita enviar conexiones de base de datos, archivos abiertos o objetos enormes. Crea los recursos necesarios dentro del proceso trabajador.

Memoria separada

Modificar una lista dentro de un proceso no modifica automáticamente la lista del proceso principal:

from multiprocessing import Process


def agregar(lista):
    lista.append(99)


if __name__ == "__main__":
    valores = [1, 2, 3]
    p = Process(target=agregar, args=(valores,))
    p.start()
    p.join()
    print(valores)  # sigue igual

Esta separación reduce ciertas condiciones de carrera, pero exige mecanismos explícitos para compartir resultados.

Comunicación con Queue

from multiprocessing import Process, Queue


def trabajador(cola, numero):
    cola.put(numero ** 2)


if __name__ == "__main__":
    cola = Queue()
    procesos = [
        Process(target=trabajador, args=(cola, n))
        for n in range(4)
    ]

    for p in procesos:
        p.start()

    resultados = [cola.get() for _ in procesos]

    for p in procesos:
        p.join()

    print(resultados)

La cola gestiona la sincronización. No uses una lista normal esperando que sea compartida.

Compartir valores pequeños

Value y Array permiten memoria compartida para tipos simples:

from multiprocessing import Process, Value


def incrementar(contador):
    for _ in range(1000):
        with contador.get_lock():
            contador.value += 1


if __name__ == "__main__":
    contador = Value("i", 0)
    procesos = [Process(target=incrementar, args=(contador,)) for _ in range(4)]

    for p in procesos:
        p.start()
    for p in procesos:
        p.join()

    print(contador.value)

El bloqueo evita actualizaciones perdidas. La memoria compartida debe ser pequeña y cuidadosamente sincronizada.

Manejar excepciones con ProcessPoolExecutor

La interfaz de concurrent.futures facilita obtener resultados y excepciones:

from concurrent.futures import ProcessPoolExecutor, as_completed


def dividir(valor):
    return 100 / valor


if __name__ == "__main__":
    with ProcessPoolExecutor(max_workers=4) as executor:
        futuros = [executor.submit(dividir, n) for n in [5, 2, 0, 4]]

        for futuro in as_completed(futuros):
            try:
                print(futuro.result())
            except ZeroDivisionError as error:
                print(f"Tarea fallida: {error}")

Si nunca llamas a result(), puedes pasar por alto una excepción del trabajador.

Comparar secuencial y paralelo

Un benchmark debe incluir el coste total de crear el pool y transferir datos:

from multiprocessing import Pool
from time import perf_counter


def tarea(numero):
    return sum(i * i for i in range(numero))


if __name__ == "__main__":
    entradas = [2_000_000] * 8

    inicio = perf_counter()
    secuencial = [tarea(n) for n in entradas]
    print("Secuencial:", perf_counter() - inicio)

    inicio = perf_counter()
    with Pool() as pool:
        paralelo = pool.map(tarea, entradas)
    print("Paralelo:", perf_counter() - inicio)

Para tareas pequeñas, la versión paralela puede ser más lenta. Multiprocessing funciona cuando el cálculo por tarea compensa la sobrecarga.

Evitar copiar datos gigantes

Enviar un DataFrame o una matriz enorme a cada trabajador puede duplicar memoria y anular la mejora. Divide archivos por rutas, utiliza lotes, memoria compartida especializada o bibliotecas numéricas que ya paralelizan internamente. No cargues todos los datos antes de repartirlos si puedes procesarlos por partes.

Finalizar correctamente

Usa gestores de contexto para pools y executors. Con procesos manuales, llama a join(). Si un trabajador queda bloqueado, investiga la causa antes de utilizar terminate(), porque una terminación abrupta puede dejar archivos o recursos en estado inconsistente.

La documentación oficial de multiprocessing explica procesos, pools, colas y memoria compartida. La documentación de ProcessPoolExecutor presenta una interfaz de alto nivel para tareas y resultados.

Errores frecuentes

Los errores más comunes son omitir el bloque __main__, enviar lambdas o funciones locales, crear demasiados procesos, compartir grandes objetos por serialización, olvidar recoger excepciones y usar paralelismo para tareas demasiado pequeñas. También debes considerar que notebooks y entornos interactivos pueden manejar los procesos de forma distinta a un script normal.

Conclusión

multiprocessing permite paralelismo real para trabajo intensivo de CPU, pero requiere dividir las tareas y limitar la comunicación. Usa funciones serializables, procesa lotes razonables, recoge resultados y mide el tiempo completo. Cuando la carga es principalmente espera, threading o asyncio suelen ser mejores; cuando el cálculo domina y puede separarse, varios procesos pueden reducir significativamente la duración.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Criação de geradores eficientes usando yield em Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    yield en Python: crea generadores eficientes

    Aprende yield en Python para crear generadores, procesar archivos grandes, construir pipelines, usar yield from y reducir el consumo de

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Decoradores en Python: guía práctica y ejemplos

    Aprende decoradores en Python con wrappers, argumentos, functools.wraps, parámetros, validación, rendimiento y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Caixas empilhadas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Módulos y paquetes en Python: guía completa

    Aprende módulos y paquetes en Python: imports, __init__.py, estructura de proyectos, ejecución con -m, imports relativos y errores circulares.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    logo do python com objetos abaixo do logo
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Programación orientada a objetos en Python

    Aprende programación orientada a objetos en Python con clases, objetos, atributos, métodos, propiedades, herencia, composición y ejemplos prácticos.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Programação assíncrona com asyncio em Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    asyncio en Python: guía práctica para principiantes

    Aprende asyncio en Python con coroutines, await, tasks, TaskGroup, timeouts, cancelación, colas, bloqueos y manejo de errores.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026
    Uso de expressões regulares regex para manipulação de texto em Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Regex en Python: guía de expresiones regulares

    Aprende regex en Python con search, fullmatch, findall, grupos, cuantificadores, sustituciones, flags, patrones compilados y ejemplos prácticos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026