The Python GIL
“What is Python’s GIL, what does it actually prevent, and when does multithreading in Python still help?”
What this tests
- That the GIL is a CPython implementation detail, not a language property
- What it serialises (bytecode execution) and what it does not (I/O, many C extensions)
- Why threads still help I/O-bound Python and hurt CPU-bound Python
- The alternatives and their costs: processes, C extensions, asyncio, free-threaded builds
Answers by level
Read the beginner answer first and notice what is missing.
The Global Interpreter Lock is a mutex in CPython (label: CPython — PyPy has one too, Jython and IronPython do not) that a thread must hold to execute Python bytecode. It exists because CPython’s memory management uses non-atomic reference counts and a lot of interpreter state that would otherwise need fine-grained locking on every object touch. One lock is simple and fast for single-threaded code, which is most Python code (How C++, JavaScript and Python Map onto the OS).
What it does *not* do: it does not stop the OS from scheduling Python threads on different cores, and it is released whenever a thread enters a blocking system call or a C function that opts out — sockets, file reads, time.sleep, hashlib, zlib, most of NumPy’s heavy kernels, database drivers. So an I/O-bound program with 50 threads genuinely overlaps its waiting, and a NumPy matrix multiply on 8 threads can use 8 cores.
What it does do: two threads running pure-Python loops take turns, so the wall-clock time is the same as one thread — often worse, because they contend for the lock and pay context switches. For CPU-bound Python the options are multiple processes (multiprocessing, ProcessPoolExecutor — separate interpreters, separate GILs, data crossed by pickling), moving the hot loop into C/Cython/Rust and releasing the GIL, or rewriting in a way that lets a library do the work.
For I/O-bound work the choice between threads and asyncio is not about the GIL at all — both are concurrency without parallelism — but about blocking libraries, code style and scale (Threads versus Async versus Processes). The failure to recognise: an async service that calls a synchronous database driver blocks the whole loop, GIL or not.
Green flags · Red flags
- Labels it a CPython detail and says why it exists (refcounts, interpreter state)
- States that it is released during blocking I/O and in many C extensions
- Says threads help I/O-bound and not CPU-bound Python, and why contention makes it worse
- Lists the alternatives with their costs (pickling, memory per process)
- Knows the free-threaded build exists
- "Python is single-threaded" or "Python cannot use multiple cores"
- Believes the GIL prevents race conditions in Python code
- Recommends asyncio as a fix for CPU-bound work
Follow-up questions
counter += 1 safe across threads?