Guzmán D. Darío Senior Python Developer English Contratar
Taller autoguiado

De bloqueante a tiempo real: un pipeline de video multi-cámara con asyncio

Paso 06 de 23
Paso 6 de 23

asyncio 101: corrutinas, await y el event loop

Vamos a parar de escribir pipeline un momento para entender la herramienta. Este paso es el más conceptual del taller, no lleva archivos que crear, y es el que hace que todo lo demás tenga sentido.

Concurrencia no es paralelismo

Esta distinción confunde a mucha gente durante años, así que vale la pena decirla clarito:

Paralelismo es varias cosas ocurriendo en el mismo instante, en varios núcleos del procesador. Dos cocineros cocinando.

Concurrencia es gestionar varias tareas a la vez, avanzando un poco en cada una, aunque solo haya un trabajador. Un cocinero que mientras el arroz hierve va picando la cebolla.

asyncio te da concurrencia en un solo hilo. No hace que tu computadora calcule más rápido: hace que deje de estar parada esperando. Y como acabas de comprobar en el paso anterior, tu pipeline se pasa casi todo el tiempo esperando.

Analogía · Un mesero, diez mesas

Un buen mesero no se queda parado en la mesa tres esperando a que los clientes se decidan. Toma el pedido de la mesa uno, pasa por la cocina a dejarlo, recoge los platos sucios de la siete de camino, y deja una cuenta en la dos.

Es una sola persona, igual que antes. Pero cada momento de espera se lo gasta atendiendo a alguien más. Nunca está quieto y nunca está en dos sitios a la vez.

Ese mesero es el event loop.

Las dos palabras nuevas

Solo hay dos cosas que aprender de sintaxis, y una tercera que entender.

async def define una corrutina. Y aquí viene lo raro: llamarla no ejecuta nada.

python python
async def read(cam):
    return await socket.read()

read("cam0")
# -> <coroutine object read at 0x104f2c9c0>

Llamar a read("cam0") te devuelve un objeto, no un resultado. Es una receta que nadie ha cocinado todavía: tiene los pasos escritos, pero no se ha encendido el fuego.

await la cocina. Ejecuta la corrutina de verdad y, cuando esta se pone a esperar algo, le devuelve el control al event loop.

python python
jpeg = await read("cam0")

Ese await es el momento clave de todo el taller. No significa "quédate aquí parado hasta que llegue el dato". Significa: "suelta el hilo, que otro lo aproveche, y avísame cuando el dato esté".

El event loop

La tercera pieza no la escribes tú: es quien reparte el trabajo. El event loop es el único hilo que ejecuta corrutinas y salta entre ellas en cada await.

Su trabajo es simple:

  1. Tiene una lista de tareas listas para avanzar.
  2. Toma una, y solo una, y la ejecuta.
  3. Cuando esa tarea llega a un await que espera algo lento, la aparta y toma la siguiente de la lista.
  4. Cuando el dato que esperaba la primera llega, la devuelve a la lista de listas.

Nadie se queda parado esperando. Y como todo ocurre en un hilo, no hay carreras de datos ni candados que gestionar.

El event loop toma una tarea de la lista de listas y la ejecuta. Al llegar a un await, la tarea se aparta a esperar y el bucle toma la siguiente. Cuando llega el dato, la tarea vuelve a la fila.
El event loop toma una tarea de la lista de listas y la ejecuta. Al llegar a un await, la tarea se aparta a esperar y el bucle toma la siguiente. Cuando llega el dato, la tarea vuelve a la fila.

Ojo

Que todo ocurra en un hilo tiene una consecuencia enorme que veremos en el paso 6: si una de tus tareas se pone a calcular sin parar, no hay ningún await donde ceder el control, y el event loop se queda congelado hasta que termine. Guárdate esa idea.

Checkpoint

Antes de seguir, asegúrate de que puedes explicar con tus palabras estas tres frases:

  • Llamar a una corrutina no la ejecuta, devuelve un objeto.
  • await la ejecuta y suelta el hilo mientras espera.
  • El event loop es un solo hilo que salta entre tareas en cada await.

En el siguiente paso lo vas a ver funcionando, con un programa de doce líneas.