Ejemplos prácticos de técnicas de concurrencia y paralelismo en Python

En este documento, exploraremos varias técnicas modernas de concurrencia y paralelismo en Python, utilizando módulos como concurrent.futures, asyncio, y threading.

1. Uso de concurrent.futures

El módulo concurrent.futures proporciona una forma de ejecutar tareas en paralelo usando hilos o procesos. Aquí mostramos un ejemplo usando ThreadPoolExecutor.

Ejemplo: Uso de ThreadPoolExecutor

from concurrent.futures import ThreadPoolExecutor
import time

def tarea(n):
    print(f'Tarea {n} iniciada')
    time.sleep(2)
    print(f'Tarea {n} finalizada')
    return n

def main():
    with ThreadPoolExecutor(max_workers=3) as executor:
        resultados = list(executor.map(tarea, range(5)))
        print('Resultados:', resultados)

if __name__ == '__main__':
    main()
    

2. Uso de asyncio

El módulo asyncio permite escribir código concurrente usando la sintaxis async/await. Es ideal para operaciones de entrada/salida no bloqueantes.

Ejemplo: Uso de asyncio con async/await

import asyncio

async def tarea(n):
    print(f'Tarea {n} iniciada')
    await asyncio.sleep(2)
    print(f'Tarea {n} finalizada')
    return n

async def main():
    tareas = [tarea(i) for i in range(5)]
    resultados = await asyncio.gather(*tareas)
    print('Resultados:', resultados)

if __name__ == '__main__':
    asyncio.run(main())
    

3. Uso de threading

El módulo threading permite la creación de hilos en Python. A continuación, mostramos un ejemplo básico de su uso.

Ejemplo: Uso de threading

import threading
import time

def tarea(n):
    print(f'Tarea {n} iniciada')
    time.sleep(2)
    print(f'Tarea {n} finalizada')

def main():
    hilos = []
    for i in range(5):
        hilo = threading.Thread(target=tarea, args=(i,))
        hilos.append(hilo)
        hilo.start()
    
    for hilo in hilos:
        hilo.join()

if __name__ == '__main__':
    main()
    

4. Comparación de técnicas

Cada técnica tiene sus propios casos de uso. Aquí hay una breve comparación:

  • concurrent.futures: Ideal para tareas de CPU-bound y IO-bound, proporciona una API simple para trabajar con hilos y procesos.
  • asyncio: Mejor para tareas IO-bound, especialmente cuando se manejan operaciones asíncronas o de red.
  • threading: Útil para tareas IO-bound pero con limitaciones en el rendimiento debido al Global Interpreter Lock (GIL) en Python.