ONE PROCESSGILCORE 1CORE 21count += 12tmp = count # read3tmp = tmp + 1 # add4count = tmp # write5with lock: count += 1THREAD Atmp –THREAD Btmp –count0LOCK 1LOCK 2waits for lock 1waits for lock 21, not 2012GIL
Threads A and B: two paths of work in one process, each with a private tmpcount: memory shared by both threadsLocks and the GIL: whoever holds the key may go onCores: a lit wire means the core is running that thread nowTwo threads, A and B, run inside one process and share the memory cell count. The core wires light in turns when one core interleaves the threads (concurrency) and together when two cores run them at once (parallelism). A value token carries count into each thread's private tmp and back: both threads read 0, so the second write overwrites the first and count ends at 1. A lock's key travels to the thread that holds it, so the other waits; with two locks taken in opposite orders each thread holds one key and waits for the other (deadlock). In standard CPython the GIL's key hops between the threads, so only one runs Python code at a time. 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.

Threads

  1. A thread is one path of work through a program. One process can run several threads, and they all share the same memory. So both threads here can read and change the same variable: count.

A race

Locks

Python

The Python behind it

;; step 1/12 · One process, two threads
import threading
count = 0                    # shared by every thread
def add():
    global count
    count += 1
a = threading.Thread(target=add)
b = threading.Thread(target=add)
a.start(); b.start()         # both run add()
a.join(); b.join()           # wait for both

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. 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.