Combinar asincronía y concurrencia en un mismo proyecto es una de las estrategias más potentes para construir sistemas eficientes en Python. En este recorrido práctico se construye un sistema de gestión de pedidos que verifica inventario, calcula costos y procesa pagos, utilizando asyncio y multiprocessing de forma conjunta.
¿Cuál es la diferencia entre concurrencia y asincronía en Python?
Aunque suenan parecido, cada concepto resuelve problemas distintos. La concurrencia ejecuta tareas de manera intercalada y cuenta con las librerías threading y multiprocessing; esta última enfocada específicamente en paralelismo para tareas con uso intensivo de CPU [0:20]. La asincronía, en cambio, permite pausar una tarea para dar paso a otra y se apoya en la librería asyncio, ideal para operaciones de entrada y salida de datos (I/O bound) [0:30].
- La concurrencia es útil cuando el CPU necesita mucho tiempo de procesamiento.
- La asincronía brilla en tareas que dependen de respuestas externas como bases de datos o servicios de pago.
- Ambas pueden combinarse en un mismo proyecto para cubrir escenarios distintos.
¿Cómo se diseña el sistema de gestión de pedidos?
El ejercicio plantea un escenario real: un sistema que debe verificar inventario, calcular el costo total y procesar pagos para múltiples órdenes de forma simultánea [1:00].
¿Cómo funciona la verificación de inventario con asyncio?
Se crea una función asíncrona llamada check_inventory que recibe un item y simula la consulta a una base de datos [1:50]. Dentro de ella se usa await asyncio.sleep() con un tiempo aleatorio generado por random.randint(3, 6) para representar la latencia del servicio. Al finalizar, retorna un valor aleatorio entre True y False mediante random.choice(), simulando la disponibilidad del producto [2:40].
python
async def check_inventory(item):
print(f"Verificando inventario para {item}...")
await asyncio.sleep(random.randint(3, 6))
print(f"Inventario verificado para {item}")
return random.choice([True, False])
¿Cómo se procesa el pago de forma asíncrona?
Otra función asíncrona llamada process_payment recibe un order_id y simula la comunicación con un servicio de pago externo [3:10]. También emplea await asyncio.sleep() para pausar la ejecución y liberar el event loop. Cuando termina, retorna True indicando que el pago fue exitoso.
python
async def process_payment(order_id):
print(f"Procesando pago para la orden {order_id}...")
await asyncio.sleep(random.randint(3, 6))
print(f"Pago procesado para la orden {order_id}")
return True
¿Por qué el cálculo del costo total usa multiprocessing?
La función calculate_total es síncrona porque simula un cálculo pesado de CPU [4:30]. Usa time.sleep(5) para representar ese procesamiento intensivo y calcula la suma de precios iterando sobre cada artículo.
python
def calculate_total(items):
print(f"Calculando costo total para {len(items)} artículos...")
time.sleep(5)
total = sum(item["precio"] for item in items)
print(f"Costo total calculado: {total}")
return total
¿Cómo se combinan asincronía y multiprocesamiento en la función principal?
La función process_order es asíncrona y orquesta todo el flujo [5:50]. Primero verifica el inventario de cada artículo usando asyncio.gather(), un método que lanza múltiples corrutinas de forma concurrente y espera a que todas terminen [6:50]. Si algún producto no está disponible, la orden se cancela.
Para el cálculo del total se crea un pool de procesos con multiprocessing.Pool, enviando la función calculate_total mediante pool.apply() y los artículos como tupla iterable [7:40]. Finalmente, el pago se procesa de forma asíncrona con await.
python
async def process_order(order_id, items):
checks = [check_inventory(item["nombre"]) for item in items]
results = await asyncio.gather(*checks)
if not all(results):
print(f"Orden {order_id} cancelada: producto no disponible")
return
with multiprocessing.Pool() as pool:
total = pool.apply(calculate_total, (items,))
payment = await process_payment(order_id)
if payment:
print(f"Orden {order_id} completada. Total: {total}")
En la función main se definen tres órdenes, cada una con un order_id y una lista de artículos con nombre y precio [9:20]. Se construye una lista de tareas iterando sobre las órdenes y se ejecutan con asyncio.gather(*tasks). Todo arranca con el event loop mediante asyncio.run(main()) [10:50].
Al ejecutar el programa, las tres órdenes se procesan de forma simultánea. Los tiempos aleatorios hacen que cada ejecución produzca resultados diferentes: algunas órdenes se completan y otras se cancelan dependiendo del random.choice en la verificación de inventario [11:30].
Si ya estás experimentando con estos patrones, comparte cómo los aplicarías en tus propios proyectos y deja tus dudas en los comentarios.