For thirty-three years, Python developers lived with a single constraint that shaped everything: the Global Interpreter Lock. The GIL meant that no matter how many CPU cores your server had — 4, 16, 128 — Python would use exactly one of them for Python bytecode execution. Multi-threaded Python programs weren't truly parallel. They were taking turns.
In October 2025, Python 3.14 shipped with a free-threaded build that is no longer experimental. The Python Steering Council accepted PEP 779, officially moving free-threaded Python from "experimental" to "supported." The GIL is still there in the default build. But for the first time, you can run production Python with true thread-level parallelism — and the ecosystem is actually ready for it.
The 40-Year-Old Lock
The GIL was introduced in CPython's earliest days as a simple solution to a hard problem: thread safety. CPython uses reference counting for memory management. When two threads modify the same object's reference count simultaneously without protection, you get memory corruption, segfaults, and undefined behavior.
The GIL solved this by making Python threads mutually exclusive. Only one thread can execute Python bytecode at a time. Other threads wait. This made CPython's memory management trivially thread-safe — at the cost of making CPU-bound multi-threading useless.
For I/O-bound workloads (web servers, network clients, file processing), the GIL was fine. Python releases the GIL during I/O operations, so threads can overlap their waiting. But for CPU-bound workloads (data processing, numerical computation, image manipulation), Python threads provided zero parallelism. You had to use multiprocessing instead — which spawns separate OS processes with separate memory spaces, separate interpreters, and all the overhead that comes with inter-process communication.
Attempts to remove the GIL go back decades. Larry Hastings' "GIL-ectomy" work in 2016 showed it was possible but incurred a 30-40% single-threaded performance penalty. The Python community's response: too expensive.
Then Sam Gross changed the math.
The Sam Gross Breakthrough
In 2021, Sam Gross — a software engineer at Meta — published a fork of CPython with the GIL removed that achieved something previous attempts hadn't: minimal single-threaded overhead. His approach used biased reference counting, deferred reference counting, and careful lock-free data structures to maintain thread safety without a global lock.
Gross's work led to PEP 703 — "Making the Global Interpreter Lock Optional in CPython" — which he submitted in January 2023. The Steering Council accepted it with a three-phase rollout plan:
| Phase | Python Version | Status | Description |
|---|
| Phase I | 3.13 (Oct 2024) | Complete | Experimental free-threaded build behind --disable-gil |
| Phase II | 3.14 (Oct 2025) | Current | Officially supported, still optional |
| Phase III | Future | Planned | Free-threaded build becomes the default |
Meta contributed engineering resources to make this happen, funding work on both the CPython implementation and ecosystem compatibility. Quansight Labs led the community coordination effort, tracking package compatibility and helping maintainers port their C extensions.
What Changed Between 3.13 and 3.14
Python 3.13 introduced the free-threaded build as experimental. It worked, but with caveats — roughly 40% single-threaded overhead, limited package compatibility, and no official support guarantees.
Python 3.14 is a different story:
Single-threaded overhead dropped from ~40% to 5-10%. The specializing adaptive interpreter (PEP 659) was re-enabled for free-threaded mode, and numerous optimizations across the interpreter brought the penalty down dramatically. On the pyperformance benchmark suite, overhead ranges from about 1% on macOS aarch64 to 8% on x86-64 Linux.
The PEP 703 implementation is complete. All the C API changes described in the PEP are finished. Temporary workarounds in the 3.13 interpreter were replaced with permanent solutions.
PEP 779 made it official. The Steering Council accepted PEP 779 in June 2025, removing the "experimental" label. This means the free-threaded build gets the same stability guarantees and bug-fix support as the default build.
The Benchmarks That Matter
The headline number everyone cites — "4x speedup!" — needs context. Here's what actual benchmarks show:
| Benchmark | Standard Python | Free-Threaded (4 threads) | Speedup |
|---|
| Prime number computation | 3.70s | 0.35s | ~10x |
| Fibonacci (4 threads) | 25-33s | 9.3s | ~3x |
| File I/O (20 files, thread pool) | 18.77s | 5.13s | ~3.6x |
| Matrix multiplication (multiprocessing baseline) | 4.49s | 6.29s | 0.7x (slower) |
That last row is important. Free-threaded Python is not faster for everything. CPU-bound tasks that are already optimized for multiprocessing can actually be slower in free-threaded mode because of the per-object locking overhead that replaces the GIL. The GIL was coarse-grained but cheap. Per-object locks are fine-grained but have overhead.
The sweet spot for free-threaded Python is workloads where:
- You have multiple CPU-bound tasks that can run in parallel
- The tasks don't heavily share mutable state
- The inter-thread communication overhead of
multiprocessing was your bottleneck
- You need shared memory access between parallel tasks
For I/O-bound web applications, the story is more nuanced. A Django benchmark with free-threaded Python 3.14 showed approximately 2x throughput improvement for request handling — real, but not transformative for most web apps that are already I/O-bound and scale fine with async.
Ecosystem Readiness: The 51% Mark
A year ago, when Python 3.13 shipped, the ecosystem was "almost completely broken" on the free-threaded build. Most pip install commands failed on anything beyond the simplest packages.
As of early 2026, 183 out of the 360 most-downloaded packages that ship native wheels have free-threaded wheels on PyPI — approximately 51% compatibility.
The critical packages are mostly ready:
| Package | Free-Threaded Status | Notes |
|---|
| NumPy | Supported since 2.1 | Some threading bottlenecks being addressed |
| SciPy | In progress | Extension modules being ported |
| pandas | In progress | Work ongoing |
| scikit-learn | In progress | Part of Quansight effort |
| Matplotlib | In progress | Part of Quansight effort |
| Cython | 3.1.0+ supported | freethreading_compatible directive |
| pybind11 | 2.13.1+ supported | gil_not_used argument |
| PyO3 (Rust) | Supported | Rust bindings work well |
| Pillow | Supported | Wheels available |
| PyYAML | Supported | Wheels available |
| SQLAlchemy | In progress | Work ongoing |
The Python Free-Threading Guide maintains an up-to-date compatibility tracker. The Quansight-Labs GitHub repository also tracks progress for scientific and ML packages specifically.
Cython 3.1.0 was a turning point. Before that release, any package using Cython extensions couldn't build on the free-threaded interpreter. Now Cython provides a freethreading_compatible compiler directive, and extensions can opt in to GIL-free operation on a per-module basis.
How to Actually Use It
Installation
With pyenv:
# Install the free-threaded build
pyenv install 3.14t-dev
pyenv local 3.14t-dev
From source:
./configure --disable-gil --enable-optimizations
make -j$(nproc)
make install
Verify it's working:
import sysconfig
print(sysconfig.get_config_var("Py_GIL_DISABLED"))
# Should print: 1
import sys
print(sys._is_gil_enabled())
# Should print: False
Your First Free-Threaded Program
import threading
import time
def cpu_work(n):
"""Compute sum of squares — pure CPU work."""
total = 0
for i in range(n):
total += i * i
return total
# Run 4 CPU-bound tasks in parallel
start = time.time()
threads = []
for _ in range(4):
t = threading.Thread(target=cpu_work, args=(10_000_000,))
threads.append(t)
t.start()
for t in threads:
t.join()
elapsed = time.time() - start
print(f"Completed in {elapsed:.2f}s")
# Standard Python: ~12s (threads run sequentially due to GIL)
# Free-threaded: ~3s (threads run in parallel on 4 cores)
Installing Packages
Most packages install normally:
pip install numpy pandas requests
For packages without free-threaded wheels, pip will try to build from source. If the package's C extensions aren't compatible, the build may fail. Check the compatibility tracker before relying on a package.
When Free-Threaded Python Makes Sense (And When It Doesn't)
| Workload | Use Free-Threaded? | Why |
|---|
| CPU-bound parallel computation | Yes | True parallelism across cores |
| Data processing pipeline | Yes | Shared memory, no IPC overhead |
| Web server (I/O-bound) | Maybe | Marginal gains over async/multiprocessing |
| NumPy-heavy computation | Not yet | NumPy already releases GIL for C operations |
| Single-threaded scripts | No | 5-10% slower for no benefit |
| C extension-heavy code | Check first | Depends on extension compatibility |
| ML training with PyTorch/TF | No | These frameworks manage their own parallelism |
The key insight: if you're already using multiprocessing and it's working fine, there's no urgent reason to switch. Free-threaded Python is most compelling when:
-
You need shared memory between parallel workers. multiprocessing requires pickling data across process boundaries. Threads share the same memory space — no serialization overhead.
-
Your parallelism is fine-grained. Spawning a process has startup cost. Threads are lightweight. If you have thousands of small parallel tasks, threads win.
-
You're building concurrent systems (not just parallel computation). Event loops, producer-consumer patterns, and shared-state coordination are natural with threads, awkward with processes.
The C Extension Challenge
The biggest risk with free-threaded Python isn't Python code — it's C extensions.
Under the GIL, C extension authors could assume that only one thread was executing Python code at a time. Many extensions aren't thread-safe because they never needed to be. With the GIL removed, those assumptions break.
The porting guide covers the technical details, but here's the summary:
Cython extensions: Use the freethreading_compatible directive in Cython 3.1.0+. This tells the interpreter your extension is safe to use without the GIL.
pybind11 extensions: Use the gil_not_used argument to create_extension_module() in pybind11 2.13.1+.
Raw C extensions: Mark your module as supporting free-threading by setting Py_mod_gil to Py_MOD_GIL_NOT_USED in your module definition.
If an extension doesn't declare free-threading support, CPython will re-enable the GIL for the entire process when that module is imported. This is the safety net: incompatible extensions can't cause data races because the GIL will protect them. But it also means one unported extension will negate free-threading for your entire application.
You can check the GIL status at runtime:
import sys
print(sys._is_gil_enabled()) # True if any module forced the GIL back on
Django, Flask, and Web Frameworks
The question everyone asks: can I run my web app with free-threaded Python?
Django is actively exploring free-threaded support. There was a DjangoCon US 2025 talk specifically about free-threaded Django. The key finding: Django's ORM and request handling work in free-threaded mode, but the question of whether to recommend it for production is still being evaluated. A benchmark showed approximately 2x throughput improvement for ASGI request handling.
Application servers are evolving too. Granian, a newer Python application server written in Rust, provides explicit free-threaded support. Gunicorn and Uvicorn are being examined for compatibility, but neither has officially declared free-threaded support yet.
For most web applications in 2026, the practical recommendation is: if you're running ASGI with an async framework and your deployment already uses multiple worker processes, the gains from free-threading will be modest. The biggest beneficiaries are synchronous Django/Flask applications running with thread-based workers, where free-threading means actual parallelism instead of GIL-serialized pseudo-parallelism.
What I Actually Think
The GIL isn't dead. It's optional. And that distinction matters more than the headlines suggest.
Here's the reality: the default Python 3.14 build still has the GIL. You have to explicitly install or build the free-threaded variant. Most packages are still catching up — 51% compatibility is progress, not parity. Web frameworks are cautiously optimistic but not recommending production use yet. And the 5-10% single-threaded overhead means you're paying a cost even when you're not using threads.
But none of that should obscure how significant this is.
Python's GIL has been the single biggest complaint about the language for two decades. It's the reason every "Python vs. Go" or "Python vs. Rust" comparison includes the paragraph about concurrency. It's the reason data scientists write multiprocessing code with shared-memory hacks instead of simple threads. It's the reason Python web servers run multiple processes instead of multiple threads.
The free-threaded build isn't perfect today. But the trajectory is clear: single-threaded overhead dropped from 40% (3.13) to 5-10% (3.14). Package compatibility went from "almost completely broken" to 51% in one year. Every major framework and build tool is adding support. Phase III — making free-threading the default — isn't a question of if, but when.
I think the practical inflection point will be Python 3.15 or 3.16. By then, the long tail of C extension packages will be ported, the single-threaded overhead will be further reduced, and web frameworks will have battle-tested their free-threaded code paths. That's when you'll see widespread production adoption.
But if you're starting a new CPU-bound Python project today — data processing, scientific computing, image pipelines — I'd start with the free-threaded build. The ecosystem for numerical work (NumPy, SciPy) is far enough along. The performance gains are real. And building for threads from the start is easier than migrating from multiprocessing later.
Sam Gross's work will be remembered as one of the most consequential contributions to CPython. He didn't just remove a lock. He proved it was possible to do so without breaking the language. The rest is just engineering.
Sources
- PEP 779 — Criteria for Supported Status for Free-Threaded Python
- PEP 703 — Making the Global Interpreter Lock Optional in CPython
- What's New in Python 3.14 — Official Documentation
- Python Support for Free Threading — Official How-To
- Python Free-Threading Guide
- Compatibility Status Tracking — Free-Threading Guide
- The First Year of Free-Threaded Python — Quansight Labs
- Enhancing the Python Ecosystem with Free Threading — Meta Engineering
- Faster Python: Unlocking the GIL — JetBrains PyCharm Blog
- Python 3.14 Free-Threading: True Parallelism — Edgar Montano
- Python 3.14: Is It Fast? — Miguel Grinberg
- Python 3.14 and the End of the GIL — Towards Data Science
- State of Python 3.13 Performance: Free-Threading — CodSpeed
- Python 3.14 and Django: 2x Throughput — Medium
- Free-Threaded Django — DjangoCon US 2025
- Python Language Summit 2023: Making the GIL Optional — PSF
- Installing Free-Threaded CPython — Guide
- Updating Extension Modules for Free-Threading — Guide
- Quansight-Labs Free-Threaded Compatibility Tracker — GitHub
- NumPy Thread Safety — NumPy 2.1 Manual
- Python 3.14 — Astral Blog
- PEP 779 Hacker News Discussion