Skip to main content
Guides

Free-Threaded Python (No-GIL) in 3.14: What It Means, Tested

By Mithun··8 min read
Quick Answer

Python 3.14 makes the free-threaded build (PEP 779 — the "no-GIL" build) an officially supported option. Without the Global Interpreter Lock, threads finally run Python bytecode truly in parallel. In my benchmark, a CPU-bound task across 4 threads went from 0.98x on the normal build to 3.91x on the free-threaded build — from "threads do nothing" to near-linear scaling. The catches: ~10% slower single-threaded, more memory, and C extensions need rebuilding. It's a separate binary (python3.14t), not the default. Check yours with sys._is_gil_enabled().

For 30 years, the one thing every Python programmer learned about threads was: they don't help with CPU-bound work, because of the Global Interpreter Lock. Python 3.14 changes that. PEP 779 promotes the free-threaded ("no-GIL") build from experimental to officially supported — a Python where threads actually run in parallel.

That's a big claim, so I measured it: the exact same threaded, CPU-bound program on the normal 3.14 build and on the free-threaded 3.14 build. The difference is not subtle. Every number below is from my machine (a 4-thread workload), run on both interpreters.

The GIL, and why threads never helped CPU work

The Global Interpreter Lock is a single mutex that lets only one thread execute Python bytecode at a time. It makes the interpreter simpler and single-threaded code fast, but it means that for CPU-bound work, adding threads buys you nothing — they just take turns holding the lock. Here's a benchmark that makes it concrete: run a heavy numeric loop as 4 serial tasks, then as 4 threads.

import sys, time
from threading import Thread
def cpu_work(n):
x = 0
for i in range(n):
x += i * i
return x
N, THREADS = 15_000_000, 4
# serial: THREADS tasks back to back
t0 = time.perf_counter()
for _ in range(THREADS):
cpu_work(N)
serial = time.perf_counter() - t0
# threaded: one task per OS thread
t0 = time.perf_counter()
ts = [Thread(target=cpu_work, args=(N,)) for _ in range(THREADS)]
for t in ts: t.start()
for t in ts: t.join()
parallel = time.perf_counter() - t0
print(f"gil_enabled={sys._is_gil_enabled()}")
print(f"serial: {serial:.2f}s")
print(f"threaded: {parallel:.2f}s -> {serial/parallel:.2f}x")
threads_cpu.py — 4 CPU-bound tasks, serial vs threaded

On the normal Python 3.14 build, threading it changes nothing — the real output:

gil_enabled=True
serial: 3.50s
threaded: 3.58s -> 0.98x
Standard build (GIL on) — real output

0.98x. Four threads, four cores, and it's *very slightly slower* than doing the tasks one after another (that's thread overhead with no parallelism to pay for it). This is the GIL in one number, and it's why CPU-bound Python has always reached for multiprocessing instead.

Free-threaded Python: the same code, 3.91x

Now the identical script, run on the free-threaded build — no code changes, just a different interpreter:

gil_enabled=False
serial: 3.86s
threaded: 0.99s -> 3.91x
Free-threaded build (GIL off) — real output

Read that again: 0.99s threaded vs 3.86s serial — a 3.91x speedup on 4 cores. The threads genuinely ran in parallel. The GIL is gone, so threading finally does for CPU-bound work what everyone always assumed it did.

This is the whole promise of PEP 779: pure-Python parallelism without spawning processes, pickling data between them, or reaching for multiprocessing. For CPU-bound workloads that share a lot of in-memory state, that's a genuinely new capability in CPython.

How to get it — and check which build you have

The free-threaded interpreter is a separate binary, python3.14t, installed alongside the normal python3.14. You opt in:

# Option 1 - python.org installer: tick "free-threaded binaries"
# (installs python3.14t next to python3.14)
# Option 2 - uv:
uv python install 3.14t
# It really is a different interpreter:
python3.14t -c "import sys; print(sys._is_gil_enabled())" # False
Installing the free-threaded build

To check what any interpreter is at runtime, two calls tell you everything:

import sys, sysconfig
print(sys._is_gil_enabled()) # True (normal) / False (free-threaded)
print(sysconfig.get_config_var("Py_GIL_DISABLED")) # 0 (normal) / 1 (free-threaded)
Am I on a free-threaded build?

A free-threaded build can also re-enable the GIL at startup — useful when a C extension isn't ready — via a flag or an environment variable (both tested):

python3.14t -X gil=1 -c "import sys; print(sys._is_gil_enabled())" # True
PYTHON_GIL=1 python3.14t -c "import sys; print(sys._is_gil_enabled())" # True
Turning the GIL back on for compatibility

Our in-browser Python compiler runs a standard (GIL) build of 3.14, so you can reproduce the 0.98x result there in seconds — the 3.91x needs the desktop python3.14t, since browser Python is single-threaded WebAssembly.

The trade-offs (it isn't free)

Free-threading is opt-in for good reasons — you pay for the parallelism:

  1. ~10% slower single-threaded. In my run the serial baseline went from 3.50s (GIL) to 3.86s (free-threaded) — about 10% overhead from per-object locking and biased reference counting. If your workload is single-threaded, you'd only lose.
  2. More memory. The machinery that replaces the single lock adds per-object bookkeeping; expect a noticeably larger footprint.
  3. C extensions must be rebuilt. Native extensions need a free-threaded (ABI) wheel — the big ones (NumPy, etc.) now ship them, but the long tail of packages may not, and an unprepared extension is why you'd re-enable the GIL.
  4. Your own code needs to be thread-safe. The GIL accidentally protected a lot of sloppy shared-state code. Real parallelism means real data races if you share mutable state without locks.

Standard vs free-threaded, at a glance

Standard buildFree-threaded build
Binarypython3.14python3.14t
sys._is_gil_enabled()TrueFalse
CPU threads (my 4-core test)0.98x (no gain)3.91x (near-linear)
Single-threaded speedbaseline~10% slower
C extensionswork as-isneed a free-threaded rebuild
Status in 3.14defaultofficially supported, opt-in

Should you switch? Reach for the free-threaded build when you have CPU-bound work that parallelises and shares in-memory state — that's where the 3.91x lives, and where multiprocessing was previously your only option. Stay on the standard build for single-threaded apps, I/O-bound work (where asyncio/threads were already fine), or anything depending on C extensions that haven't shipped free-threaded wheels yet. It's supported now, but the ecosystem is still catching up — test before you ship.

Run Python 3.14 in your browser — free

Our online compiler runs Python 3.14 (a standard GIL build), so you can reproduce the threading demo instantly. No install, nothing uploaded.

Open the Python Compiler

Free tools mentioned here

Related guides

Frequently asked questions

What is free-threaded Python?

Free-threaded Python is a build of CPython (officially supported from 3.14 via PEP 779) that runs without the Global Interpreter Lock, so multiple threads can execute Python bytecode in parallel across CPU cores. It's a separate binary, python3.14t, installed alongside the normal interpreter — not the default.

Does removing the GIL make Python faster?

Only for multi-threaded, CPU-bound work. In a 4-thread benchmark it turned a 0.98x 'speedup' into 3.91x. But single-threaded code is about 10% slower on the free-threaded build, and it uses more memory, so for single-threaded or I/O-bound programs there's no benefit — and a small cost.

How do I check if my Python has the GIL?

Call sys._is_gil_enabled() — it returns True on a normal build and False on a free-threaded one. You can also read sysconfig.get_config_var('Py_GIL_DISABLED'), which is 0 on the standard build and 1 on the free-threaded build.

How do I install free-threaded Python 3.14?

Tick 'free-threaded binaries' in the official python.org installer, or run uv python install 3.14t. Either way you get a python3.14t executable next to the regular python3.14. On the free-threaded build you can re-enable the GIL with the -X gil=1 flag or PYTHON_GIL=1 environment variable if a C extension needs it.

Will my code and libraries work on free-threaded Python?

Pure-Python code runs immediately. C extensions must ship a free-threaded (ABI) wheel — major libraries like NumPy already do, but many packages don't yet, which is why the free-threaded build lets you turn the GIL back on. Your own multi-threaded code also has to be genuinely thread-safe, since the GIL no longer masks data races.

Is the GIL gone for good in Python 3.14?

No — the GIL build is still the default in 3.14. PEP 779 makes the free-threaded build officially supported as an opt-in alternative. The long-term plan is a multi-year, phased transition; for now you choose per-install which interpreter to run.

Keep reading