Los ejercicios 1-3 se hacen en un script suelto (
race_lab.py); el 4-6, dentro de tu TicketFlow. No mires solutions.md hasta entregar.
Preparación:
cd ~/dev/ticketflow && source .venv/bin/activateEjercicio 1 — La carrera del contador
Copia esto en race_lab.py y ejecútalo 5 veces:
import threading
counter = 0
def increment(n):
global counter
for _ in range(n):
counter += 1
t1 = threading.Thread(target=increment, args=(100_000,))
t2 = threading.Thread(target=increment, args=(100_000,))
t1.start(); t2.start(); t1.join(); t2.join()
print(f"esperado 200000, obtenido {counter}")- Anota los 5 resultados. ¿Alguno dio exactamente 200.000? ¿Por qué varían entre ejecuciones?
- Arregla la carrera con
threading.Lock()(bloquea la sección que lee-suma-escribe). ¿Deja de variar? - ¿En qué se convierte el rendimiento tras el lock? (mide con
time.perf_counter()).
Ejercicio 2 — GIL: CPU vs I/O
- CPU: dos hilos que cada uno suma en bucle 10 millones de veces. Mide el tiempo total y compáralo con un solo hilo haciendo el doble de trabajo. ¿Escaló?
- I/O: dos hilos que cada uno hace
time.sleep(1)(simula una llamada a la BD). Mide el total. ¿Escaló? - Escribe en una línea la conclusión que explicarías en una entrevista.
Ejercicio 3 — La ventana check-then-act, observada
- Escribe una versión pequeña del patrón con hilos (sin BD):
import threading, time
reservas = []
seat_ocupado = False
def reserva(usuario):
global seat_ocupado
if not seat_ocupado: # CHECK
time.sleep(0.001) # ← la ventana (simula la query a la BD)
reservas.append(usuario) # ACT
seat_ocupado = True
hilos = [threading.Thread(target=reserva, args=(u,)) for u in range(20)]
for h in hilos: h.start()
for h in hilos: h.join()
print(f"{len(reservas)} reservas para UN asiento")- ¿Cuántas reservas se crean? Ajusta el
sleep(0.0001, 0.01): ¿cómo cambia el desastre? - Arregla el experimento de dos formas: (a) lock en memoria; (b) convirtiendo el check+act en una única operación atómica (pista: ¿qué "operación atómica única" sería en una BD real? nómbrala, no hace falta implementarla).
Ejercicio 4 — Demuestra la I1 con peticiones reales
En tu TicketFlow real (la constraint de la 00b ya protege):
- Abre dos terminales y lanza en paralelo (mismo asiento libre, dos usuarios):
# terminal 1
curl -s -X POST http://127.0.0.1:8000/api/events/1/reserve/ -H 'Content-Type: application/json' \
-d '{"seat_id": 5, "user": "ana"}' & \
# terminal 2 (lanza inmediatamente después)
curl -s -X POST http://127.0.0.1:8000/api/events/1/reserve/ -H 'Content-Type: application/json' \
-d '{"seat_id": 5, "user": "beto"}'- ¿Qué respondió cada una? ¿Hubo algún 500? ¿O una recibió 201 y la otra 409? Si viste un 500 con
IntegrityError, eso es un bug de diseño de API (Lección 01): la constraint salvó los datos, pero la vista no sabía traducir el fallo. - Añade el manejo: captura
IntegrityErroren la vista y responde 409 Conflict con un cuerpo consistente. Pega tu diff.
Ejercicio 5 — Deadlock en miniatura (dos locks con orden inverso)
- Ejecuta esta versión "segura" y confirma que termina:
import threading
lock_a = threading.Lock() # fila del asiento
lock_b = threading.Lock() # fila del usuario
def op1(): # siempre A → B
with lock_a, lock_b:
pass
hilos = [threading.Thread(target=op1) for _ in range(10)]
for h in hilos: h.start()
for h in hilos: h.join()
print("sin deadlock")- Crea
op2()que tome B → A y lanza 1 hilo de cada en bucle. ¿Se cuelga? (si no se cuelga en 10 s, fuerza la ventana con untime.sleep(0.001)entre adquisiciones). - Arregla el deadlock imponiendo orden global (todas las funciones A → B). ¿Sigue colgándose?
Ejercicio 6 — Async o sync (razonado)
Para cada caso, decide sync o async en Django y justifica en 1-2 líneas:
GET /api/events/— listado que consulta PostgreSQL.POST /api/payments/webhook/— verifica firma y encola el trabajo pesado en Celery.GET /api/exchange-rates/— llama a 3 APIs externas de divisas y agrega el resultado.POST /api/reports/export/— genera un CSV de 2 GB en memoria y comprime.
Ejercicio 7 — Mapa de concurrencia de tu proyecto
Dibuja (texto) el mapa de la Lección 6 con TU configuración: cuántos workers de Gunicorn usarías para TicketFlow en una VM de 2 vCPU / 4 GB, con qué --threads, y qué cola de Celery aparte. Justifica cada número.
Entrega
Pega resultados y diffs en el chat. Al corregir cierra la 03 y abre la Lección 04 — Estructuras de datos y algoritmos (con el carrito de asientos como ejemplo).