Concurrencia en la cocina de medianoche
·
Resumen del curso
1
La cola de la primera noche: concurrencia y paralelismo
2
Contratar más cocineros: lo básico de threading
3
La pizarra no cuadra: condiciones de carrera y Lock
4
Solo tres fogones: herramientas de sincronización
5
Cuchillos y tablas: interbloqueo
6
El riel de comandas: queue
7
Cocineros de agencia: concurrent.futures
8
Por qué la masa no va más rápido: el GIL y el paralelismo
9
Una sola encargada de sala: primeros pasos con asyncio
10
Se cae la reserva de grupo: concurrencia estructurada
11
La sala se congela: llamadas bloqueantes y combinar herramientas
12
La noche de la crítica: todo junto
Tiempo 0:00–0:50
La comanda de esta noche
#01 · Primera noche
Problema
Nada más abrir, ocho mesas. El segundo de cocina prepara los pedidos uno por uno, solo. Mientras hierve la sopa, se queda de pie esperando.
Objetivo
Sacar los ocho pedidos en menos de 1 segundo.
Restaurante nocturno Dongsi
Primera noche, diez en punto. En cuanto se abre la puerta, entra una avalancha de clientes. Pones al fuego la sopa del primer pedido y te quedas delante de la olla hasta que hierve. Tres minutos después sale el primer plato, y empiezas el segundo pedido.
La chef Yoon Seoyeon mira su temporizador y dice: «Mientras hervía la sopa, ¿qué hacían tus manos? Nada. Ese es el problema de esta noche.»
Imitemos en Python la espera de la sopa. time.sleep(0.3) significa «esperar 0,3 segundos sin hacer nada». Piensa que hemos comprimido tres minutos en 0,3 segundos.
import time
def make_order(n):
time.sleep(0.3) # esperar a que hierva la sopa
return f"#{n}"
start = time.perf_counter()
done = [make_order(n) for n in range(1, 9)]
print(f"{time.perf_counter() - start:.2f}s", done)
# 2.40s ['#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8']time.perf_counter() es el reloj que se usa para medir tiempos. Lo lees dos veces, restas, y obtienes los segundos que pasaron entre medias.Cada ejecución puede variar un poco en los últimos decimales. El resultado de un ejemplo que mide tiempos siempre es «aproximado».
El trabajo de una cocina se divide en dos tipos.
| Trabajo de espera (I/O) | Trabajo de manos (CPU) | |
|---|---|---|
| En la cocina | Que hierva la sopa, que se hornee algo, esperar una entrega | Amasar, cortar en juliana |
| En un programa | Respuestas de red, leer archivos, bases de datos | Cálculos, compresión, procesar imágenes |
| Mientras tanto | La persona está ociosa | La persona sigue ocupada |
La concurrencia ahorra mucho tiempo cuando hay mucho trabajo de espera: mientras hierve una sopa, pones otra olla al fuego. El trabajo de manos solo se acelera si de verdad hay más personas, y ahí Python tiene una regla escondida. De eso hablaremos en la lección 8.
Las dos palabras se mezclan a menudo, pero significan cosas distintas.
El paralelismo es una forma de concurrencia. En una cocina donde casi todo es espera, basta con concurrencia para ir mucho más rápido, aunque solo haya un par de manos. En la línea de tiempo de abajo, alterna entre «Solo, uno por uno» y «Varios cocineros». Cada barra es un pedido y el eje horizontal es el tiempo.
Línea de tiempo de la cocina
Pedidos
6
Cocineros
3
Cocinando
Esperando
Cancelado
Falló
time.sleep, y se registra el instante real en que cada pedido empezó y terminó.Si todos los pedidos duran lo mismo, el tiempo total es «el número máximo de pedidos que le tocan a un cocinero × la duración de un pedido».
import math
def estimate(orders, cooks, each=0.3):
rounds = math.ceil(orders / cooks) # máximo de pedidos por cocinero
return round(rounds * each, 2)
for cooks in (1, 3, 4, 8, 16):
print(cooks, estimate(8, cooks))
# 1 2.4
# 3 0.9
# 4 0.6
# 8 0.3
# 16 0.3Python tiene tres herramientas para hacer varias cosas a la vez. Hoy solo las probamos; a partir de la próxima lección entramos a fondo en cada una.
import threading
threads = [threading.Thread(target=make_order, args=(n,)) for n in range(1, 9)]
for th in threads:
th.start() # ocho cocineros empiezan a la vez
for th in threads:
th.join() # esperar a que terminen todos
# unos 0.30 simport asyncio
async def make_order_async(n):
await asyncio.sleep(0.3) # mientras espera, atiende otros pedidos
return f"#{n}"
async def main():
return await asyncio.gather(*(make_order_async(n) for n in range(1, 9)))
print(asyncio.run(main()))
# ['#1', '#2', '#3', '#4', '#5', '#6', '#7', '#8'] (unos 0.31 s)| Herramienta | En la cocina | Adecuada para | Lecciones |
|---|---|---|---|
threading | Varios cocineros en la misma cocina | Trabajo de espera, código antiguo | 2–7 |
multiprocessing | Una cocina sucursal que hasta tiene su propia nevera | Trabajo de manos (CPU) | 8 |
asyncio | Una encargada de sala que atiende sola decenas de mesas | Muchísimo trabajo de espera | 9–11 |
El consejo de la chef
Más cocineros, más rápido, eso es verdad. Pero la cocina se vuelve más ruidosa. ¿Qué pasa si dos personas escriben un número en la misma pizarra a la vez? Lo vas a ver con tus propios ojos en la lección 3.
Sumar cocineros tiene su precio.
El objetivo de esta noche era sacar ocho pedidos en menos de 1 segundo. Como todos son «trabajo de espera», basta con que ocho cocineros se encarguen de uno cada uno.
start = time.perf_counter()
threads = [threading.Thread(target=make_order, args=(n,)) for n in range(1, 9)]
for th in threads:
th.start()
for th in threads:
th.join()
print(f"{time.perf_counter() - start:.2f}s")
# 0.30sLa chef detiene el temporizador. «De 2,4 a 0,3 segundos. Nada mal para el primer día. Pero cuando haya ocho cocineros, ¿quién lleva la cuenta de cuántos platos hizo cada uno?»
Diario de servicio — Primera noche. Los 8 pedidos eran trabajo de espera; se los repartieron 8 cocineros y todo salió en 0,3 s. Mañana contratamos cocineros de verdad.
time.sleep(0.3) de make_order por un bucle que sume números (trabajo de manos) y mide si se acelera al ejecutarlo con hilos. Si el resultado te parece raro, en la lección 8 sabrás por qué.La tarjeta de recetas de la chef
El trabajo de espera (I/O) se acelera mucho con concurrencia. El trabajo de manos (CPU) necesita paralelismo de verdad.
Concurrencia = avanzar alternando; paralelismo = ejecutar en el mismo instante.
Medir tiempos: start = time.perf_counter() … time.perf_counter() - start
Tres herramientas: threading (varios cocineros), multiprocessing (varias cocinas), asyncio (una sola persona en la sala).
La concurrencia no sale gratis: trae coste de gestión y errores nuevos.
Recibe avisos de nuevas lecciones
Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.
R
ReflexKR
Aprende Python y Reflex haciendo. Todos los ejemplos se ejecutaron y verificaron en Reflex 0.10.0.
ReflexKR es gratis y sin banners publicitarios. Si te ayudó, puedes apoyarlo con un café. ☕ Invítanos un café
Aviso de afiliados© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Preguntas y respuestas
Pregunta lo que quieras sobre esta lección. Aquí se reúnen las preguntas de todas las versiones de idioma de esta página.