Explanation
What just happened
Two threads crunched numbers in pure Python. To run Python code, a thread must hold the GIL, and only one can. So a ran while b waited, then after 5 ms the GIL passed to b. Plenty of cores, one busy.
Try the what-if switch
- I/O work: the threads sleep instead of crunching.
time.sleepand blocking I/O let go of the GIL, so both threads wait at the same time and only grab the GIL to print. - python3.15t: the same code on the free-threaded build. With no GIL, both threads crunch at once, on two cores.
Why there’s a GIL
It keeps CPython’s internals simple and single-threaded code fast. The cost: pure-Python CPU work uses one core per process.
Watch out
Free-threading removes the GIL, and with it the accidental safety. On python3.15t, two threads running count += 1 lose updates. Use a threading.Lock.
Common questions
What is the GIL in Python?
The global interpreter lock: only the thread holding it runs Python code. On the standard build, two CPU-bound threads take turns on one core.
How often does Python switch threads?
A thread is asked to hand the GIL over after the switch interval, 5 ms by default (sys.getswitchinterval() returns 0.005).
Are Python threads useless then?
No. Sleeping and blocking I/O release the GIL, so threads that mostly wait on the network do overlap.
Verified: this timeline matches a real program, checked in CI on Python 3.14, 3.15 and free-threaded 3.15t. How we verify. Unclear or wrong? Tell us