Threads & asyncAdvanced

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

Strong green flag · Explains the convoy effect on I/O latency in a mixed CPU/I/O program.
Green 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
Red flags
  • "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

F1
Does the GIL make counter += 1 safe across threads?
F2
A NumPy workload scales to 8 cores with threads. How?
F3
Why does a gunicorn deployment use processes and not threads?

Scenario

A Flask service does JSON parsing and image resizing per request. With a 32-thread worker on 8 cores it uses one core and p99 latency is high. Propose two changes and predict their effect on CPU use and memory.

Learn this topic