Exercises 1-3 run in a loose script (
race_lab.py); 4-6 go inside your TicketFlow. Do not look at solutions.md before submitting.
Setup:
cd ~/dev/ticketflow && source .venv/bin/activateExercise 1 — The counter race
Copy this into race_lab.py and run it 5 times:
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"expected 200000, got {counter}")- Write down the 5 results. Did any give exactly 200,000? Why do they vary between runs?
- Fix the race with
threading.Lock()(lock the section that reads-adds-writes). Does it stop varying? - What does performance become after the lock? (measure with
time.perf_counter()).
Exercise 2 — GIL: CPU vs I/O
- CPU: two threads, each summing in a loop 10 million times. Measure the total time and compare with a single thread doing twice the work. Did it scale?
- I/O: two threads, each doing
time.sleep(1)(simulates a DB call). Measure the total. Did it scale? - Write in one line the conclusion you would give in an interview.
Exercise 3 — The check-then-act window, observed
- Write a small thread-based version of the pattern (no DB):
import threading, time
reservations = []
seat_taken = False
def reserve(user):
global seat_taken
if not seat_taken: # CHECK
time.sleep(0.001) # ← the window (simulates the DB query)
reservations.append(user) # ACT
seat_taken = True
threads = [threading.Thread(target=reserve, args=(u,)) for u in range(20)]
for t in threads: t.start()
for t in threads: t.join()
print(f"{len(reservations)} reservations for ONE seat")- How many reservations get created? Tune the
sleep(0.0001, 0.01): how does the disaster change? - Fix the experiment two ways: (a) an in-memory lock; (b) turning the check+act into a single atomic operation (hint: what would the "single atomic operation" be in a real DB? name it, no need to implement it).
Exercise 4 — Prove I1 with real requests
On your real TicketFlow (the 00b constraint already protects it):
- Open two terminals and fire in parallel (same free seat, two users):
# 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 (fire immediately after)
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"}'- What did each one answer? Any 500? Or did one get 201 and the other 409? If you saw a 500 with
IntegrityError, that is an API design bug (Lesson 01): the constraint saved the data, but the view did not know how to translate the failure. - Add the handling: catch
IntegrityErrorin the view and respond 409 Conflict with a consistent body. Paste your diff.
Exercise 5 — Deadlock in miniature (two locks, reverse order)
- Run this "safe" version and confirm it finishes:
import threading
lock_a = threading.Lock() # the seat's row
lock_b = threading.Lock() # the user's row
def op1(): # always A → B
with lock_a, lock_b:
pass
threads = [threading.Thread(target=op1) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print("no deadlock")- Create
op2()that takes B → A and launch 1 thread of each in a loop. Does it hang? (if it doesn't hang within 10 s, force the window with atime.sleep(0.001)between acquisitions). - Fix the deadlock by imposing a global order (all functions A → B). Does it still hang?
Exercise 6 — Async or sync (reasoned)
For each case, decide sync or async in Django and justify in 1-2 lines:
GET /api/events/— a listing that queries PostgreSQL.POST /api/payments/webhook/— verifies the signature and enqueues the heavy work in Celery.GET /api/exchange-rates/— calls 3 external currency APIs and aggregates the result.POST /api/reports/export/— generates a 2 GB CSV in memory and compresses it.
Exercise 7 — Your project's concurrency map
Draw (text) the map from Lesson 6 with YOUR configuration: how many Gunicorn workers you would use for TicketFlow on a 2 vCPU / 4 GB VM, with which --threads, and which separate Celery queue. Justify each number.
Submit
Paste results and diffs in the chat. When corrected, Lesson 03 closes and Lesson 04 — Data structures and algorithms opens (with TicketFlow's seat cart as the example).