Python GIL - Global Interpreter Lock
Let’s understand a few things beforehand before diving deep into it.
Threads
Threads are sequences of execution of code that can be executed independently without waiting for other tasks to finish.
It is the smallest unit of tasks that can be executed by an OS. A program can be single-threaded or multi-threaded.
Process
A program under execution is called a process. It can have multiple programs.
What is the difference between single-threaded and multi-threaded?
in single-threaded programs, only one task is executed at a time. In Multi-threaded architecture, multiple tasks are executed at a time.
single-threaded programs can be imagined as single lines going from the entry point of the application to its end while multi-threaded program is like a tree where the whole app starts from point 1 then it branches out more and more.
Python GIL:
Global Interpreter Lock
Python Interpreter is like a Virtual Machine. The thread is basically a POSEX or OS thread.
The interpreter doesn’t manage the thread by itself, it gives instructions to the OS and manages the thread.
Let’s say we have two threads T1 and T2 both trying to acquire the memory space (A situation of Conflict). Instead of allowing both threads to write in a memory location, we can put a lock. Only the thread, which is having the lock can write to memory (suppose T1 has a lock). Once the writing from T1 is done, we can release the lock and put it on T2.
So, GIL tries to manage the memory which the interpreter occupies.
In python, threads aren’t running in parallel. Threads are running in serial.
Before we talk about serial execution above, let’s talk about GIL.
The Memory Manager which is run by interpreter isn’t thread safe. Here, GIL performs a check on state of the thread whether the thread is running/waiting. Based on the state of the thread, GIL proceeds ahead.
GIL keeps on checking the thread on 100 ticks. Tick is bytecode-instruction executed by interpreter.
[1 Tick = 1 bytecode-instruction]
GIL checks for 100 bytecode-instructions and when the ins are executed, it checks whether the thread is waiting or still running.
import dis → to know # of ticks
We can see, threads are getting executed in a serial order. It’s not multi-threading but Coordinated Multi-tasking, facilitated by GIL.
How? In the graph, these are all different tasks that are trying to do different things and they’re having some coordination between them.
So, multi-threading in Python is actually Coordinated Multi-Tasking.
The check for 100 ticks is validated for Python 2, it is removed in advent of Python3. As there are many cons here (see in graph)
→ T3 was waiting for a certain point to get GIL
Suppose there were few more threads, then it might be possible that there will be few threads who will be waiting for acquiring GIL for a longer period of time
First disadvantage → Long Wait for GIL!!
so, the low-priority thread might starve for the GIL lock and CPU time
Second disadvantage → When a thread releases the GIL it signals the waiting threads to acquire the GIL.
Suppose thread at T3 releases the GIL it will signal to both threads T1 and T2 that the GIL is free and ready for acquisition. So, the threads T1 and T2 will be in battle to acquire GIL and CPU time.
Third disadvantage → Signaling overhead! (release signal to thread, waiting for threads to acquire the signal, etc.)
To overcome this, now we have Python 3.2 which don’t have tick approach. Now we have what is called, the Fixed Time Limit Approach.
In this, we have a Fixed Time for each thread (5ms of execution time).
In this case, waiting time and execution time is same for all threads.
So, the battle for acquiring GIL is eliminated. Also, there is no starvation to acquire GIL or CPU time. Every thread will get equal amount of CPU time and GIL. No signaling happening in this case, so the overhead is also eliminated.
One major Disadvantage!
CPU Bound threads are more prioritized as compared to I/O Bound threads(starves!). Also called, priority inversion.
Update:
So, GIL has long been a controversial feature in Python, as it limits true parallelism in CPU-bound tasks due to the lock that prevents multiple native threads from executing Python bytecode at the same time. This has been particularly limiting for CPU-intensive tasks like numerical computations or image processing.
IN the Python 3.13 release (October 8, 2024) they have attempted experimental GIL-free mode to remove this limitation. It will allow threads to execute Python code concurrently on multiple cores, improving the performance of multi-threaded applications.
Check release here: https://www.python.org/downloads/release/python-3130/
That’s all for now! Will be updating with more such articles :)
- himanshu
8 Oct 2024