The new feature in Python 3.13 allowing CPython to run without the Global Interpreter Lock

Think Different - Dhiraj Patra
4 min readJun 24, 2024

--

Understanding Free-threaded CPython and Parallel Execution

The new feature in Python 3.13, allowing CPython to run without the Global Interpreter Lock (GIL), is significant for improving parallelism in Python programs. Here’s a detailed explanation along with a code example to illustrate how it works and the benefits it brings:

Key Points

1. Disabling the GIL: CPython can be built with the ` — disable-gil` option, allowing threads to run in parallel across multiple CPU cores.

2. Parallel Execution: This enables full utilization of multi-core processors, leading to potential performance improvements for multi-threaded programs.

3. Experimental Feature: This is still experimental and may have bugs and performance trade-offs in single-threaded contexts.

4. Optional GIL: The GIL can still be enabled or disabled at runtime using the `PYTHON_GIL` environment variable or the `-X gil` command-line option.

5. C-API Extensions: Extensions need to be adapted to work without the GIL.

Demo Code Example

To demonstrate, let’s create a multi-threaded program that benefits from the free-threaded execution.

```python

import sysconfig

import sys

import threading

import time

# Check if the current interpreter is configured with — disable-gil

is_gil_disabled = sysconfig.get_config_var(“Py_GIL_DISABLED”)

print(f”GIL disabled in build: {is_gil_disabled}”)

# Check if the GIL is actually disabled in the running process

is_gil_enabled = sys._is_gil_enabled()

print(f”GIL enabled at runtime: {is_gil_enabled}”)

# Define a function to simulate a CPU-bound task

def cpu_bound_task(duration):

start = time.time()

while time.time() — start < duration:

pass

print(f”Task completed by thread {threading.current_thread().name}”)

# Create and start multiple threads

threads = []

for i in range(4):

thread = threading.Thread(target=cpu_bound_task, args=(2,), name=f”Thread-{i+1}”)

threads.append(thread)

thread.start()

# Wait for all threads to complete

for thread in threads:

thread.join()

print(“All tasks completed.”)

```

How it Helps with Parallelism and Software

1. Enhanced Performance: By disabling the GIL, this allows true parallel execution of threads, utilizing multiple cores effectively, which can significantly improve performance for CPU-bound tasks.

2. Scalability: Programs can scale better on modern multi-core processors, making Python more suitable for high-performance computing tasks.

3. Compatibility: Existing code may require minimal changes to benefit from this feature, particularly if it already uses threading.

4. Flexibility: Developers can choose to enable or disable the GIL at runtime based on the specific needs of their application, providing greater flexibility.

Practical Considerations

- Single-threaded Performance: Disabling the GIL may lead to a performance hit in single-threaded applications due to the overhead of managing locks.

- Bugs and Stability: As an experimental feature, it may still have bugs, so thorough testing is recommended.

- C Extensions: Ensure that C extensions are compatible with the free-threaded build, using the new mechanisms provided.

In summary, the free-threaded CPython in Python 3.13 offers significant potential for improving the performance of multi-threaded applications, making better use of multi-core processors and enhancing the scalability of Python programs.

Yes, the new free-threaded CPython feature can be beneficial when used in conjunction with parallelism via processes, although the primary advantage of disabling the GIL directly applies to multi-threading. Here’s a brief overview and an example demonstrating how parallelism with processes can be combined with the new free-threaded CPython:

Combining Free-threaded CPython with Multiprocessing

Key Points

1. Multi-threading vs. Multiprocessing:

- Multi-threading: Removing the GIL allows threads to run truly in parallel, making threading more efficient for CPU-bound tasks.

- Multiprocessing: The `multiprocessing` module spawns separate Python processes, each with its own GIL, enabling parallel execution across multiple cores without the need to disable the GIL.

2. Combining Both: Using free-threaded CPython can optimize CPU-bound tasks within a single process, while multiprocessing can distribute tasks across multiple processes for additional parallelism.

Code Example

Here’s an example combining threading and multiprocessing:

```python

import sysconfig

import sys

import threading

import multiprocessing

import time

# Check if the current interpreter is configured with — disable-gil

is_gil_disabled = sysconfig.get_config_var(“Py_GIL_DISABLED”)

print(f”GIL disabled in build: {is_gil_disabled}”)

# Check if the GIL is actually disabled in the running process

is_gil_enabled = sys._is_gil_enabled()

print(f”GIL enabled at runtime: {is_gil_enabled}”)

# Define a function to simulate a CPU-bound task

def cpu_bound_task(duration):

start = time.time()

while time.time() — start < duration:

pass

print(f”Task completed by thread {threading.current_thread().name}”)

# Wrapper function for multiprocessing

def multiprocessing_task():

# Create and start multiple threads

threads = []

for i in range(4):

thread = threading.Thread(target=cpu_bound_task, args=(2,), name=f”Thread-{i+1}”)

threads.append(thread)

thread.start()

# Wait for all threads to complete

for thread in threads:

thread.join()

# Create and start multiple processes

processes = []

for i in range(2): # Adjust the number of processes as needed

process = multiprocessing.Process(target=multiprocessing_task)

processes.append(process)

process.start()

# Wait for all processes to complete

for process in processes:

process.join()

print(“All tasks completed.”)

```

Benefits and Use Cases

1. Maximized CPU Utilization: Using threading within processes allows for full utilization of multi-core processors, both at the thread and process level.

2. Improved Performance: This hybrid approach can significantly improve performance for CPU-bound tasks, especially in scenarios requiring heavy computation.

3. Scalability: Programs can scale effectively, distributing tasks across multiple cores and processes.

Practical Considerations

- Resource Management: Ensure proper management of resources to avoid excessive context switching or memory overhead.

- Complexity: Combining threading and multiprocessing can add complexity to the code, so it’s important to handle synchronization and communication between threads and processes carefully.

- Compatibility: Verify that all components, including C extensions, are compatible with the free-threaded build if you decide to disable the GIL.

By leveraging both threading and multiprocessing, you can achieve efficient parallelism and fully exploit modern multi-core hardware, especially with the enhancements brought by the new free-threaded CPython.

You can find more related article in my blog. Search https://dhirajpatra.blogspot.com

--

--

Think Different - Dhiraj Patra

I am a Software architect for AI, ML, IoT microservices cloud applications. Love to learn and share. https://dhirajpatra.github.io