← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Python GIL & Free Threading

Python Python 3.13+ Advanced
debt(d9/e7/b7/t7)
d9 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'silent in production until users hit it' (d9). No linter warns you that your threaded CPU-bound code fails to scale; py-spy and sys._is_gil_enabled only help after you suspect a problem. The lack of speedup is silent until benchmarked under load.

e7 Effort Remediation debt — work required to fix once spotted

Closest to 'cross-cutting refactor across the codebase' (e7 minus 0 = e7). The quick_fix says swap threading for multiprocessing/ProcessPoolExecutor, but this changes data-sharing semantics (pickling, shared memory), touches worker lifecycle, and often ripples through the codebase. For C extensions, adding Py_mod_gil support is a per-extension rework.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (b7). Concurrency model choice shapes every future feature: data structures, IPC, deployment, library selection. Applies across web/cli/queue/library contexts, and switching later is painful.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception is explicit: developers assume Python threads run in parallel like Java/Go threads — contradicting how threading works in nearly every other mainstream language. The 'obvious' way (add threads for speedup) is wrong for CPU-bound work.

About DEBT scoring →

Also Known As

GIL Global Interpreter Lock PEP 703 no-GIL Python free-threaded CPython

TL;DR

The GIL serializes Python bytecode across threads; PEP 703 free-threading (3.13+) removes it, enabling true parallelism.

Explanation

CPython's Global Interpreter Lock (GIL) is a mutex that permits only one thread to execute Python bytecode at a time. Even on a 16-core machine, a pure-Python multithreaded workload runs on effectively one core - threads take turns holding the GIL, releasing it every few bytecode instructions or when doing I/O. This is why threading in CPython is great for I/O-bound work (network calls, file reads release the GIL) but useless for CPU-bound work, where multiprocessing or C extensions (numpy, which releases the GIL inside native code) are needed.

PEP 703, accepted for CPython 3.13, introduces an official free-threaded build (python3.13t) that removes the GIL entirely. It replaces GIL-protected reference counting with biased reference counting and deferred reference counting, uses per-object locks for containers, and adds atomic operations for critical sections. In this build, threads run Python bytecode truly in parallel across cores. Free-threading is experimental in 3.13, opt-in via a separate build, and comes with a single-threaded performance cost (roughly 5-10% slower per thread) that is expected to shrink in 3.14+.

Practical impact: existing pure-Python code mostly just works, but C extensions must be recompiled and marked GIL-safe (Py_mod_gil slot). Libraries with global mutable state, unsynchronized caches, or lazy singletons need audit. Data races become possible where they were previously masked by the GIL - for example, dict updates from multiple threads. Use threading.Lock, queue.Queue, or immutable data structures deliberately.

For now, treat the standard 3.13 build as GIL-enabled and the -t suffix build as free-threaded. Check with sys._is_gil_enabled(). Choose asyncio for I/O concurrency, multiprocessing or concurrent.futures.ProcessPoolExecutor for CPU work on GIL builds, and consider free-threading builds for CPU-bound workloads that share large data structures too expensive to fork.

Common Misconception

Python threads run in parallel on multiple cores. In standard CPython builds they do not - the GIL serializes bytecode so only one thread executes Python code at a time; parallelism requires multiprocessing, native extensions that release the GIL, or the new free-threaded build.

Why It Matters

Misunderstanding the GIL leads engineers to add threads expecting CPU speedup and getting none, or to ship C extensions that crash under free-threaded builds. Choosing the right concurrency model is the difference between linear scaling and no scaling at all.

Common Mistakes

  • Using threading for CPU-bound work and expecting multi-core speedup - the GIL serializes execution, so use multiprocessing or ProcessPoolExecutor instead.
  • Assuming dict or list operations are thread-safe because the GIL protects them - compound operations like check-then-set still race.
  • Shipping C extensions without declaring Py_mod_gil support, so they force the GIL back on under free-threaded 3.13.
  • Benchmarking free-threaded Python on single-threaded code and concluding it is slower - the point is scaling across cores, not per-thread speed.
  • Forgetting that I/O and native calls (numpy, requests, sqlite) already release the GIL, so threading is genuinely useful for those workloads.

Avoid When

  • Avoid free-threaded builds in production until critical C extensions declare Py_mod_gil support.
  • Avoid threading for pure-Python CPU-bound work on standard CPython - it will not scale.
  • Avoid assuming GIL-based thread safety when writing code intended to run on future free-threaded interpreters.
  • Avoid mixing multiprocessing and threading without careful design - forked child processes with live threads deadlock easily.

When To Use

  • Use threading (with GIL) for I/O-bound workloads where threads spend most time waiting on network or disk.
  • Use multiprocessing or ProcessPoolExecutor for CPU-bound work on standard CPython builds.
  • Consider free-threaded 3.13+ builds for CPU-bound workloads sharing large in-memory data structures too costly to duplicate via fork.
  • Use asyncio when you have thousands of concurrent I/O operations and want low per-task overhead.

Code Examples

✗ Vulnerable
import threading
import time

def cpu_bound(n):
    # Pure Python arithmetic - holds the GIL
    total = 0
    for i in range(n):
        total += i * i
    return total

# Expect 4x speedup on a 4-core machine. Get ~1x.
start = time.perf_counter()
threads = [threading.Thread(target=cpu_bound, args=(10_000_000,))
           for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(f'Threaded: {time.perf_counter() - start:.2f}s')
# Threads serialize on the GIL - no faster than sequential
✓ Fixed
import sys
from concurrent.futures import ProcessPoolExecutor

def cpu_bound(n):
    total = 0
    for i in range(n):
        total += i * i
    return total

if __name__ == '__main__':
    # Check runtime: standard build vs free-threaded (python3.13t)
    gil_on = getattr(sys, '_is_gil_enabled', lambda: True)()

    if gil_on:
        # Standard CPython: use processes for CPU-bound work
        with ProcessPoolExecutor(max_workers=4) as pool:
            results = list(pool.map(cpu_bound, [10_000_000] * 4))
    else:
        # Free-threaded 3.13+: threads run truly in parallel
        from concurrent.futures import ThreadPoolExecutor
        with ThreadPoolExecutor(max_workers=4) as pool:
            results = list(pool.map(cpu_bound, [10_000_000] * 4))

Added 27 Jul 2026
Views 32
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 3 pings M 1 ping T 1 ping W 6 pings T 2 pings F 0 pings S 0 pings S 0 pings M 1 ping T 1 ping W 0 pings T 0 pings F
No pings yet today
No pings yesterday
Google 5 ChatGPT 4 Applebot 2 Meta AI 2 Ahrefs 1 PetalBot 1
crawler 15
🧱 FUNDAMENTALS — new to this? Start with the ground floor.
Python general Python is a programming language known for readable syntax and versatility, used for web development, data science, automation, and more.

Python's gentle learning curve makes it an ideal first language, while its vast ecosystem keeps it relevant for machine learning, APIs, and DevOps. Skills transfer directly to professional environments because Python runs in production at companies of every size.

💡 When Python throws IndentationError, check that every block uses the same whitespace style—pick spaces (preferably 4) and stick with them everywhere.

Ask Codex about Python →
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: High
⚡ Quick Fix
For CPU-bound work on standard CPython, use multiprocessing or ProcessPoolExecutor; test critical libraries on python3.13t before relying on free-threaded builds.
📦 Applies To
python 3.13 web cli queue-worker library
🔗 Prerequisites
🔍 Detection Hints
threading\.Thread\s*\([^)]*target=
Auto-detectable: ✗ No py-spy sys._is_gil_enabled
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: High Context: File Tests: Regenerate


✓ schema.org compliant