Why Python threads don't run in parallel (the GIL)

Read the explanation
What if

Two threads crunch numbers. They take turns holding the GIL, so only one core is ever busy.

Predict first

Two threads each crunch numbers in pure Python on an 8-core machine. How many cores do they keep busy?

012345678start a, bjoin()returncrunchthread a: waits for the GILwaitscrunchthread a: waits for the GILwaitsthread a: crunch, donedonethread b: waits for the GILwaitscrunchthread b: waits for the GILwaitscrunchthread b: waits for the GILwaitsthread b: crunch, donedoneheld by aheld by bheld by aheld by bheld by aheld by b

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
  • Blocked
  • Holding lock
Step 1/5

main starts two threads that crunch numbers in pure Python.

1def crunch(name):2    total = 03    for i in range(20_000_000):  # pure Python CPU work4        total += i5    print(f"{name} done")6 7def main():8    a = threading.Thread(target=crunch, args=("a",))9    b = threading.Thread(target=crunch, args=("b",))10    a.start()11    b.start()12    a.join()13    b.join()14 15if __name__ == "__main__":16    main()

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

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