The Python GIL
A short blog post about this oft-discussed topic: the GIL in Python.
The GIL, or the Global Interpreter Lock, is a mutex that prevents two Python threads from executing Python bytcode at once. A mutex is a structure that ensures that only one thread can enter a critical (not thread-safe) section of a code at any given time. It can be used to protect data from concurrent threads accessing it, creating inconsistencies and unwanted behaviour. So in essence, Python treats most of the bytecode as critical and doesn’t allow running multiple threads at a time.
This mutex on bytecode means that Python can’t, by default, take advantage of multi-core systems and so your quad-core CPU might not be any faster than a single core machine when it comes to running Python applications, even if you’re using a library like
threading. However, with the multiprocessing module, you can effectively spawn multiple subprocesses and leverage multiple CPUs, sidestepping the GIL. However, with
multiprocessing comes the resource overhead of running an additional process.
It’s important to note that if you have a process which is I/O-bound - eg. it does network calls, or database queries, or reads/writes to a file - then you can still take advantage of multiple threads and do other things with a new thread while your original thread is waiting for I/O. This is possible because Python releases the GIL for I/O operations. For instance, if you’re firing off 10 HTTP requests as part of a web crawler, you don’t need to wait for the first one to finish before issuing the next one. You can pipeline them and use 10 threads and they will be, from a practical perspective, concurrent, and not blocking. A standard way of doing this kind of work is by using the
concurrent.futures.ThreadPoolExecutor class to coordinate the work and gather the results in a dictionary.
The limitation is a hard limit when it comes to CPU-bound processes. You can’t get around that, unless you are using
multiprocessing - but then again, if you are doing something really CPU intensive, you are probably not using pure Python but something like
numpy, where many calculations are not affected by the GIL at all. This is handled internally and you don’t have to worry about it. Creators of C extensions for Python do have to worry about it, and if they want to release the GIL to use multiple cores, they have to write thread-safe code.
The GIL was necessary for the memory management aspect of Python: since Python uses reference counting for garbage collection, race conditions might result in memory leaks or even incorrectly freed memory (wiped data).
An important end-note is that the GIL is technically not a feature of Python but that of the implementation of CPython. Other Python implementations might use different techniques for memory management, and thus they might not need the GIL.
Further resources for this topic: