Threads and concurrency
One program can do several things at once by running threads: separate paths of work that share the same memory. Watch two threads take turns on one core and run side by side on two, then step through the classic bug where both add 1 to a shared counter and one update vanishes. Fix it with a lock, see how two locks can freeze a program in a deadlock, and learn what CPython's GIL changes.
5 quick questions
Pick an answer to see why. Your score saves with your progress.
01Two threads run on a computer with a single CPU core. What does the core do?
One core can run one thread at a time. The operating system switches it between threads many times a second, so both make progress by interleaving. Running at the same instant needs two or more cores (parallelism).
02count starts at 0. Two threads each run count += 1 at the same time, with no lock. What can count end up as?
count += 1 is read, add, write. If both threads read 0 before either writes, both write 1 and one update is lost. Whether that happens depends on timing, which is what makes it a race condition.
03What does a lock (mutex) guarantee around count += 1?
A thread must take the lock to enter. Any other thread that asks for it waits until it is released, so each thread's three steps run without another thread's steps in between.
04Thread A holds lock 1 and waits for lock 2. Thread B holds lock 2 and waits for lock 1. What is this called?
Each thread waits for something the other holds, so neither can ever move. Taking locks in the same order in every thread prevents this cycle.
05In standard CPython, why do threads not speed up a program that only does calculations?
The global interpreter lock lets one thread execute Python code at a time. Threads still help when they wait for input or output. Python 3.13 added an optional free-threaded build without the GIL (PEP 703).
Glossary
- Thread
- One path of work through a program. A process can run several threads, which share its memory.
- Process
- A running program, with its own memory. All of its threads can read and change that memory.
- Concurrency
- Several tasks making progress by taking turns, for example two threads sharing one CPU core.
- Parallelism
- Several tasks running at literally the same moment, on different CPU cores.
- Race condition
- A bug where the result depends on the timing of threads, such as two threads both writing 1 when count should be 2.
- Lock (mutex)
- Short for mutual exclusion: only the thread holding it may go on; others that ask for it wait until it is released.
- Deadlock
- Threads that each hold a lock and wait for a lock another one holds, so none of them can ever continue.
- GIL
- CPython's global interpreter lock: it lets only one thread run Python code at a time.
Notes and sources
- The figure’s tmp is each thread’s private working copy. Every thread has its own stack and registers; memory such as count is shared by all the threads of a process.
- Python does not promise that count += 1 happens in one go. Standard CPython asks the running thread to let another run about every 5 milliseconds (sys.getswitchinterval() is 0.005), but recent standard builds only hand over at certain points, such as loops and function calls, so this exact line rarely loses updates there. On the free-threaded build, in languages like C or Java, or when a call sits between the read and the write, updates are lost. That is why you still need a lock.
- If the six small steps of two threads could interleave in any order, 18 of the 20 possible orders would lose an update. Only the two orders in which one thread finishes before the other starts give 2.
- In practice the bad timing can be rare, which makes race conditions hard to find: a program can pass its tests many times and still fail later.
- The GIL is released while a thread waits for input or output, so threads still help with I/O-bound work. For CPU-bound work, Python’s docs suggest multiprocessing or concurrent.futures.ProcessPoolExecutor.
- The free-threaded build (PEP 703) was experimental in Python 3.13 and became officially supported in 3.14 (PEP 779). It is a separate build; the standard build still has the GIL. Without the GIL, a lock is still needed for count += 1.
- Python library: threading (Thread, Lock, the GIL note)
- Python glossary: global interpreter lock
- Python library: sys.setswitchinterval
- Python library: dis (bytecode disassembler)
- PEP 703: Making the Global Interpreter Lock Optional in CPython
- What's New in Python 3.13: free-threaded CPython
- PEP 779: Criteria for supported status for free-threaded Python
- Python HOWTO: Python support for free threading
- OSTEP ch. 26: Concurrency, an introduction (University of Wisconsin)
- OSTEP ch. 28: Locks
- OSTEP ch. 32: Common concurrency problems (deadlock)
Quiz Unlocks after the last step.