R

ReflexKR

Documentación de Reflex

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

Concurrencia en la cocina de medianoche
Lección 1

Tiempo 0:00–0:50

La cola de la primera noche: concurrencia y paralelismo

La comanda de esta noche

#01 · Primera noche

La cola de la 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.»

1-1. 0,3 segundos por pedido; ¿y con ocho? (6 min)

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.
  • Ocho pedidos de 0,3 segundos, uno tras otro: 0,3 × 8 = 2,4 segundos. De esos 2,4 segundos, el segundo de cocina casi no usó las manos. Todo fue espera.

Cada ejecución puede variar un poco en los últimos decimales. El resultado de un ejemplo que mide tiempos siempre es «aproximado».

1-2. Trabajo de espera y trabajo de manos (8 min)

El trabajo de una cocina se divide en dos tipos.

Trabajo de espera (I/O)Trabajo de manos (CPU)
En la cocinaQue hierva la sopa, que se hornee algo, esperar una entregaAmasar, cortar en juliana
En un programaRespuestas de red, leer archivos, bases de datosCálculos, compresión, procesar imágenes
Mientras tantoLa persona está ociosaLa 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.

1-3. Concurrencia y paralelismo no son lo mismo (8 min)

Las dos palabras se mezclan a menudo, pero significan cosas distintas.

  • Concurrencia (concurrency): avanzar con varias tareas alternándolas, de modo que desde fuera parece que progresan juntas. Un cocinero que deja una sopa al fuego y, mientras tanto, remueve otra olla.
  • Paralelismo (parallelism): ejecutar varias tareas de verdad en el mismo instante. Cuatro cocineros amasando cada uno su masa a la vez.

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ó

8 pedidos de 0,3 s cada uno. Solo y uno por uno, las barras forman una única fila larga; con más cocineros se reparten en varias filas que se solapan y todo termina antes.
  • Con «Solo, uno por uno», las ocho barras van pegadas en una sola fila y tarda unos 2,4 segundos.
  • Con «Varios cocineros» y 3 cocineros, tres filas se solapan y tarda unos 0,9 segundos; con 8, unos 0,3 segundos.
  • Este banco de pruebas no es un dibujo. En el servidor, hilos de verdad ejecutan time.sleep, y se registra el instante real en que cada pedido empezó y terminó.

1-4. ¿Cuánto se gana con más cocineros? (5 min)

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.3
  • Con 3 cocineros, 8 ÷ 3 redondeado hacia arriba da 3 rondas: 0,9 segundos. El mismo número que viste en la línea de tiempo.
  • Con más cocineros que pedidos, ya no se gana nada. La duración de un pedido (0,3 segundos) es el suelo.
  • Si los pedidos duran distinto, el suelo es el pedido más largo. Por mucho que repartas el trabajo, no puedes terminar antes que el trozo más largo.

1-5. Un anticipo de las tres herramientas (8 min)

Python 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 s
import 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)
HerramientaEn la cocinaAdecuada paraLecciones
threadingVarios cocineros en la misma cocinaTrabajo de espera, código antiguo2–7
multiprocessingUna cocina sucursal que hasta tiene su propia neveraTrabajo de manos (CPU)8
asyncioUna encargada de sala que atiende sola decenas de mesasMuchísimo trabajo de espera9–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.

1-6. La concurrencia no sale gratis (5 min)

Sumar cocineros tiene su precio.

  • Coste de gestión: crear hilos y turnarlos también lleva tiempo. Si los pedidos son muy cortos, incluso puede ir más lento.
  • Errores nuevos: si dos personas tocan el mismo ingrediente a la vez, el resultado sale mal (lección 3). Si cada uno espera la herramienta que tiene el otro, la cocina se detiene (lección 5).
  • Coste mental: se rompe la suposición de que el código se ejecuta línea a línea, en orden. Por eso este curso comprueba siempre en la línea de tiempo «qué se ejecutó y cuándo».

Cierre (10 min)

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.30s

La 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.

Ejercicios

  1. En la línea de tiempo, pon 12 pedidos y 4 cocineros. Calcula primero cuánto debería tardar y luego ejecútalo.
  2. Cambia el 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é.
  3. Anota tres «trabajos de espera» y tres «trabajos de manos» de tu día a día.

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.

← Anterior

Resumen del curso

Siguiente →

Lección 2 · Contratar más cocineros: lo básico de threading

Recibe avisos de nuevas lecciones

Te avisaremos por correo cuando este curso tenga lecciones nuevas o se abra un curso de continuación.

Datos: tu correo electrónico · Finalidad: avisos de nuevas lecciones o de la publicación de este curso · Conservación: hasta que pidas que lo borremos. No lo usamos para nada más ni lo compartimos. Responde a cualquier aviso y lo borraremos enseguida.

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.

Se muestra como texto plano, sin enlaces ni formato.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex