How Python's GIL Actually Limits (and Doesn't Limit) Your Threads
CPython's Global Interpreter Lock lets only one thread execute Python bytecode at a time, which matters enormously for CPU-bound code and barely at all for I/O-bound code.
CPython's Global Interpreter Lock, or GIL, is a single lock around the interpreter's core execution loop that only one thread can hold at a time. Whichever thread holds the GIL is the only one allowed to execute Python bytecode at that instant, even on a machine with many CPU cores and even if you have started several Python threads. This exists largely because CPython's memory management, including the reference counting this site's garbage collector piece covers, is not thread-safe without it; the GIL is what lets multiple threads safely increment and decrement the same reference counts without corrupting them.
For CPU-bound work, tight loops doing math, image processing, parsing large amounts of data in pure Python, this means threads do not give you the parallelism you would expect. Two threads both crunching numbers on separate cores still take turns holding the GIL, switching back and forth every fixed number of bytecode instructions or after a timed interval, so the total work does not run meaningfully faster than a single thread doing it serially, and the constant hand-off can even make it slower.
The GIL does not, however, block all concurrency. CPython releases the GIL during blocking I/O operations, reading a file, waiting on a network socket, sleeping, specifically so other threads can run while one thread is waiting on something outside the CPU. That is why threads remain genuinely useful for I/O-bound workloads like a web server handling many slow network requests concurrently: the threads are not competing for CPU time, they are mostly waiting, and the GIL gets released during that wait.
There are also ways around the GIL's limits for CPU-bound work specifically. C extensions, including many numeric libraries, can release the GIL explicitly while running compute-heavy code written in C, letting that portion run in true parallel with other Python threads. And Python's multiprocessing module sidesteps the GIL entirely by running separate processes instead of threads, each with its own Python interpreter and its own GIL, at the cost of needing to serialize data to pass it between them rather than sharing memory directly.
