GIL or Global Interpreter Lock can be disabled in Python version 3.13. This is currently experimental.
What is GIL? It is a mechanism used by the CPython interpreter to ensure that only one thread executes the Python bytecode at a time.
An Experimental Feature
Python 3.13 brings major new features compared to Python 3.12 and one of them is free-threaded mode, which disables the Global Interpreter Lock, allowing threads to run more concurrently.
This is an experimental feature and if you want to try it, you can download the beta version of Python 3.13 from here.
At the time of installation, check the option “free threaded binaries(experimental)” to get the feature in Python.
Making GIL Optional in Python 3.13
The GIL will be disabled when you configure the Python with the --disable-gil
option which is nothing but a build configuration (free threading build) at the time of installation.
This will allow optionally enabling and disabling GIL using the environment variable PYTHON_GIL
which can be set to 1
and 0
respectively.
It will also provide a command-line option -X gil
which can also be set to 0
(disable) and 1
(enable).
1 2 3 4 5 6 |
# v3.13 # GIL disabled python3 -X gil=0 sample.py # GIL enabled python3 -X gil=1 sample.py |
We can also check if the current interpreter is configured with the free threading build (--disable-gil
) by using the following code.
1 2 |
import sysconfig print(sysconfig.get_config_var("Py_GIL_DISABLED")) |
If we run this, we’ll get either 0
which means GIL is enabled, or 1
which means GIL is disabled.
With this, we’ll also get a function that can be used to check if GIL is disabled in the running process.
1 2 |
import sys sys._is_gil_enabled() # returns a boolean value |
GIL Vs No GIL
Let’s see how the performance of multi-threaded programs will be affected when GIL is enabled and disabled.
We have a simple Python program (gil.py
) that computes the factorial of numbers and compares the execution time taken by single-threaded, multi-thread, and multi-process tasks. We’ll run this Python program first with GIL and then without GIL.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
import sys import sysconfig import math import time import threading import multiprocessing def compute_factorial(n): return math.factorial(n) # Single-threaded def single_threaded_compute(n): for num in n: compute_factorial(num) print("Single-threaded: Factorial Computed.") # Multi-threaded def multi_threaded_compute(n): threads = [] # Create 5 threads for num in n: thread = threading.Thread(target=compute_factorial, args=(num,)) threads.append(thread) thread.start() # Wait for all threads to complete for thread in threads: thread.join() print("Multi-threaded: Factorial Computed.") # Multi-process def multi_processing_compute(n): processes = [] # Create a process for each number in the list for num in n: process = multiprocessing.Process(target=compute_factorial, args=(num,)) processes.append(process) process.start() # Wait for all processes to complete for process in processes: process.join() print("Multi-process: Factorial Computed.") def main(): # Checking Version print(f"Python version: {sys.version}") # GIL Status status = sysconfig.get_config_var("Py_GIL_DISABLED") if status is None: print("GIL cannot be disabled") if status == 0: print("GIL is active") if status == 1: print("GIL is disabled") numlist = [100000, 200000, 300000, 400000, 500000] # Single-threaded Execution start = time.time() single_threaded_compute(numlist) end = time.time() - start print(f"Single-threaded time taken: {end:.2f} seconds") # Multi-threaded Execution start = time.time() multi_threaded_compute(numlist) end = time.time() - start print(f"Multi-threaded time taken : {end:.2f} seconds") # Multi-process Execution start = time.time() multi_processing_compute(numlist) end = time.time() - start print(f"Multi-process time taken : {end:.2f} seconds") if __name__ == "__main__": main() |
Running gil.py
with GIL
1 2 3 4 5 6 7 8 9 |
python gil.py Python version: 3.12.2 (tags/v3.12.2:6abddd9, Feb 6 2024, 21:26:36) [MSC v.1937 64 bit (AMD64)] GIL cannot be disabled Single-threaded: Factorial Computed. Single-threaded time taken: 9.04 seconds Multi-threaded: Factorial Computed. Multi-threaded time taken : 8.21 seconds Multi-process: Factorial Computed. Multi-process time taken : 5.64 seconds |
We have Python v3.12 in which there is no option to check the GIL status, so we got “GIL cannot be disabled”.
The difference is not that much between the single-threaded and multi-threaded but we can see a pretty decent difference in the case of multi-process task.
Running gil.py
without GIL
1 2 3 4 5 6 7 8 9 |
D:/SACHIN/Python13/python3.13t gil.py Python version: 3.13.0b3 experimental free-threading build (tags/v3.13.0b3:7b41395, Jun 27 2024, 16:17:17) [MSC v.1940 64 bit (AMD64)] GIL is disabled Single-threaded: Factorial Computed. Single-threaded time taken: 9.28 seconds Multi-threaded: Factorial Computed. Multi-threaded time taken : 4.86 seconds Multi-process: Factorial Computed. Multi-process time taken : 6.14 seconds |
This time we have a third beta version of Python 3.13 configured with free threading build and as we can see the GIL is disabled.
But the most important part is we can see a massive difference in the performance of multi-threaded task and on the other side, some degradation can be seen in the performance of multi-process and single-threaded task.
πOther articles you might be interested in if you liked this one
β Create multi-threaded Python programs using a threading module.
β Template inheritance in Flask.
β Difference between exec and eval in Python.
β Create temporary files and directories using tempfile module in Python.
β Upload and display images on the frontend using Flask.
β How does the learning rate affect the ML and DL models?
That’s all for now
Keep Codingββ