Threads, processes and the GIL
A process is a running program with its own memory; threads are workers inside it that share that memory. Step through an eight-line program, count.py, in which two threads each add 1 to count, and watch CPython's global interpreter lock (the GIL) let only one of them run Python code at a time. See how a badly timed switch loses an update, why threads still help with waiting, and how a second process gets its own interpreter, memory and GIL so two cores can work at once.
5 quick questions
Pick an answer to see why. Your score saves with your progress.
01Two threads of one CPython process both run a long, CPU-heavy loop. On a machine with two cores, what happens on the usual build?
The GIL lets only one thread run Python bytecode at a time, so CPU-heavy threads take turns rather than running in parallel. multiprocessing (or ProcessPoolExecutor) is the way to use both cores.
02Why can count += 1, run in two threads at once, end with count one too small?
count += 1 compiles to several instructions: a read (LOAD_GLOBAL), a push of the 1 (LOAD_SMALL_INT on 3.14), an add (BINARY_OP) and a write (STORE_GLOBAL). If both threads read the same value before either writes, one update is lost. A threading.Lock around the update prevents it.
03A child started with multiprocessing.Process runs count += 1. What does the parent's count show afterwards, if it was 0?
Processes do not share memory. The child has its own copy of count; to get a result back, send it through a multiprocessing Queue or Pipe, or use a Pool's return values.
04Your program downloads 50 web pages and spends most of its time waiting for the network. Which tool fits best?
A thread that waits for I/O releases the GIL, so other threads run meanwhile. Threads are light and share memory, which suits I/O-bound work.
05What does sys.getswitchinterval() return on a default CPython?
The switch interval is 0.005 seconds (5 ms) by default. It is an ideal, not a promise, and the operating system picks which waiting thread runs next.
Glossary
- Process
- A running program with its own private memory. python count.py starts one; multiprocessing starts more.
- Thread
- A worker inside a process that follows the code one line at a time. Threads of one process share its memory.
- Main thread
- The thread every Python process starts with; threading.Thread adds more.
- GIL (global interpreter lock)
- CPython's lock that lets only one thread run Python bytecode at a time. Each process has its own.
- Race condition
- A bug where the result depends on how threads happen to interleave, such as a lost update to a shared counter.
- Lock (mutex)
- threading.Lock: only one thread at a time may run the code inside with lock:, so shared data stays consistent.
- I/O-bound / CPU-bound
- I/O-bound work mostly waits (network, disk); CPU-bound work mostly computes. Threads suit the first, processes the second.
- Free-threaded build
- An optional build of CPython (3.13+) that can run without the GIL (PEP 703).
Notes and sources
- The GIL belongs to CPython, the standard Python from python.org, not to the Python language. Since Python 3.13 an optional free-threaded build of CPython can run without the GIL (PEP 703); the usual build still has it.
- sys.setswitchinterval() changes the time slice. It is an ideal length, not a promise, and the operating system decides which waiting thread runs next: CPython has no scheduler of its own.
- CPython switches threads only between bytecode instructions (and in recent versions checks for a switch only at some of them), so one instruction such as L.append(x) is safe; count += 1 is several instructions. A lost update is rare, but rare is not never: use a Lock whenever threads change shared data.
- multiprocessing starts children with spawn on Windows and macOS and with forkserver on Linux since Python 3.14. Both import your file again in the child, which is why the start code sits under if __name__ == "__main__":.
- concurrent.futures offers the same choice behind one interface: ThreadPoolExecutor for waiting work, ProcessPoolExecutor for computing work.
- Python glossary: global interpreter lock
- Python library: threading (Thread, join, Lock, the GIL note)
- Python library: multiprocessing (processes, start methods, Queue and Pipe)
- Python library: sys.setswitchinterval
- Python FAQ: What kinds of global value mutation are thread-safe?
- Python library: dis (bytecode instructions)
- Python library: concurrent.futures
- Python HOWTO: Python support for free threading
- PEP 703: Making the Global Interpreter Lock Optional in CPython
Quiz Unlocks after the last step.