Skip to content
Workshop · 8 chapters · about 2.5 hours · Python 3.13

Concurrency, from first principles.

Threads, the GIL, race conditions, locks, deadlock, queues, the event loop, limits and cancellation. Each chapter explains one idea in depth, then hands you a real bug in a busy kitchen to fix. Otto, the octopus line cook, checks your work.

real bugs to fix
8
real bugs to fix
packages to install
0
packages to install
start to finish
2.5 h
start to finish
Fig. 1 · Case 02time in ms
01234
cook A
checktillsell
cook B
checktillsell
stock
10-1
Both cooks check while one croissant is left (dashed box), then both sell it. Stock ends at -1. In the lab, fifty cooks do this at once.
01 · Contents

Eight chapters, eight bugs.

Read the idea, run the case, watch it break, fix it, and let the checker run it many times. Every symptom below is real output from the lab.

  1. 01Waiting is not workingDinner for twenty takes two secondsthreads, processes, the GIL20 min
  2. 02Race conditionsTen croissants, fifty soldcheck-then-act, locks20 min
  3. 03Hold locks brieflyThe lock that closed the kitchencontention, critical sections15 min
  4. 04DeadlockKnife, then boarddeadlock, lock ordering20 min
  5. 05Queues: producers and consumersThe orders nobody cookedqueues, sentinels, backpressure20 min
  6. 06The event loopThe waiter who stood stillasyncio, blocking the loop20 min
  7. 07Limits and backpressureFifty orders at oncesemaphores, fan-out limits15 min
  8. 08Timeouts and cancellationThe burners that never came backtimeouts, cancellation, cleanup20 min
02 · Explorer

Lose an update yourself.

Two cooks each run plated += 1, which Python runs as three steps. You are the scheduler: choose who goes next. Then turn on the lock and try to break it again.

Try A, B, A, B, A, B: both cooks load 0 before either one stores. That is chapter 02 in six clicks.

Sharedplated = 0
no lock

Cook A

  1. local = plated
  2. local = local + 1
  3. plated = local

local = 0

Cook B

  1. local = plated
  2. local = local + 1
  3. plated = local

local = 0

Order of steps

Step the cooks in any order. Can you make plated end at 1?

03 · Method

Run it. Fix it. Check it many times.

  1. Step 1 · run

    Watch the bug happen on your own machine.

    $ kitchen run 2

    croissants sold: 50

    stock left: -40

  2. Step 2 · fix

    Change the case file in lab/cases/. The chapter explains the idea; the fix is yours to write.

    with self.lock:

    if self.stock > 0:

    ...

  3. Step 3 · check

    The checker runs the case many times. A race has to lose every time.

    $ kitchen check 2

    PASS in 20 rushes, exactly 10 croissants are sold every time

    PASS the stock never goes below zero

    PASS the till still takes time (keep the sleep)

04 · Measured

GIL on, GIL off.

The same code on Python 3.13 and on the free-threaded Python 3.14, on one laptop (16 CPUs, Windows 11). You run both in chapters 01 and 02.

kitchen try stove

Four CPU-heavy jobs. Lower is faster.

Ran onGIL onGIL off
one thread0.98 s1.10 s
4 threads1.07 s0.27 s
4 processes0.31 s0.29 s
kitchen try counter

Four threads each add 1, a million times, with no lock.

expected
4,000,000
GIL on
4,000,000
GIL off
1,098,299

The GIL hid the race. It never prevented it.

05 · Checklist

Ten questions before you ship.

  1. 01Is the slow part waiting or working? Threads and asyncio for waiting, processes for working.
  2. 02List every piece of data more than one thread or task touches.
  3. 03Hand data over with a queue before you share it with a lock.
  4. 04A check and the action that depends on it go in one critical section.
  5. 05Hold locks briefly. Never wait on the network while holding one.
  6. 06Take locks in one agreed order, written down.
  7. 07Never block the event loop. Every async def awaits its waits.
  8. 08Cap concurrency. Unlimited fan-out is a bug waiting for traffic.
  9. 09Put a timeout on every wait, and clean up in finally.
  10. 10Test it many times. A race that passes once has proven nothing.
06 · Start

Ready in a minute.

You need uv and git. uv installs Python for you, and the lab uses the standard library only.

Start chapter 01
shell
git clone https://github.com/jars-demo/concurrency-demo.git
cd concurrency-demo/lab
uv sync
uv run python -m kitchen run 1