Sign in

Just when I thought I was out...

Despite a lot of talk about the removal of the GIL in Python, it is actually still the default execution mechanism in Python. But what is it and what does it do?

from concurrent.futures import ThreadPoolExecutor

def work1():
    total = 0  # local total, not shared with work2
    for _ in range(100):
        total -= 1  # new total is made, ref-count update
        print(total)

def work2():
    total = 0  # local total, not shared with work1
    for _ in range(100):
        total += 1  # new total is made, ref-count update
        print(total)

with ThreadPoolExecutor() as executor:
    executor.submit(work1)
    executor.submit(work2)

From looking at this code, you might expect the Python Interpreter to start and run two process threads and the output suggests it does:

.
77
-45
78
79
80
-46
-47
.

But actually, the threads run in a single process, giving control to each other at regular intervals. This works somewhat like asyncio but with two important differences:

  1. CPython automatically locks and releases the GIL when a thread executes Python bytecode. In contrast, asyncio runs on a single thread, so there is no need to explicitly lock or release the GIL.
  2. Thread preemption: a thread can be interrupted by the scheduler to run another thread (preemptive multitasking). Async functions, on the other hand, yield control cooperatively back to the scheduler.

Since the interpreter must lock the GIL when running threads, using multiple threads for CPU-bound Python code does not achieve true parallelism, and might even hurt performance due to the overhead of task switching.

Why must the GIL be locked when running threads?

The GIL protects the interpreter, not your code. When bytecode runs in two threads simultaneously:

Without the GIL, memory corruption could occur and memory safety cannot be guaranteed.

The golden rule

If Python bytecode is executing, the GIL must be held. But there are exceptions.

When will the GIL be released immediately?

Threads can release the GIL during blocking I/O operations to allow another thread to run simultaneously. Examples include:

What will change in the future?

When the GIL is removed or made optional, Python will achieve true parallelism for CPU-bound code. To make this possible:

Compatibility considerations

Written by Loek van den Ouweland on Feb. 4, 2026.