Python GIL: What It Blocks and What 3.14t Changes
The Python GIL is a CPython mutex: one OS thread runs bytecode at a time. Default 3.14 still has it; 3.14t is supported opt-in.

The Python GIL is a CPython mutex: one OS thread runs bytecode at a time. Default 3.14 still has it; 3.14t is supported opt-in.

The Python GIL (Global Interpreter Lock) is a CPython mutex that lets one OS thread run Python bytecode at a time. Default 3.14 still has it. The free-threaded 3.14t build is supported opt-in under PEP 779 as of June 2025, not the default.
The Python Software Foundation ships CPython. The Steering Council accepted PEP 703 (Sam Gross) in October 2023 and PEP 779 in June 2025. If you spawn threads for CPU-bound Python, they serialize; blocking I/O and well-behaved C already drop the lock.
3.14t is officially supported and still optional.The global interpreter lock is a mutex that protects access to Python objects, so multiple threads cannot execute Python bytecode at once. People say “Python has a GIL.” They mean CPython.
Jython and IronPython have no GIL. PyPy still has one.
Threads still exist under the GIL. They take turns on bytecode via sys.setswitchinterval(). A thread must hold the GIL before it touches Python objects or calls the C API.
Guido van Rossum added the lock in 1992, when typical machines had one core. The design traded a lock on every object for one process-wide mutex, which kept the C API simple and reference counting cheap.
The precise cut is whether this thread is executing CPython bytecode right now.
Holds the GIL (serialized) | Releases the GIL (can overlap) |
|---|---|
Pure-Python loops, attribute access, int math | Blocking I/O ( |
Two Python threads counting primes | Well-behaved C ( |
CPU-bound Python on 16 cores → ~1 core of bytecode | NumPy inner loops, hashlib-in-C, image codecs |
A C extension that never drops the lock |
|
On r/Python, the recurring correction is still the CPU/I/O split.
"GIL only allows one CPU-bound thread to execute at a time. I/O bound threads can multi-thread." (u/gerardwx in r/Python, Nov 2025)
A C extension that holds the lock inside one long call looks GIL-bound and is not a Python-bytecode problem. C-API threads document the other side: the lock is released around blocking I/O via Py_BEGIN_ALLOW_THREADS.
The GIL does not make your code thread-safe. Thomas Wouters, speaking at PyCon US 2026, said you have no control over when Python releases and then reacquires the GIL, and that it mostly does not protect C or C++ extensions either.
PEP 684 subinterpreters (3.12+) give you one GIL per interpreter, not one GIL gone. That is a different multi-core path, not free-threading.
CPython’s memory model is reference counting plus a shared object graph. Without a lock, two threads could increment, decrement, or deallocate the same object. One mutex around bytecode was cheaper than a lock on every PyObject.
PEP 703 states the cost in 2023 language: the GIL prevents multiple threads from executing Python code at the same time, and that is an obstacle to using multi-core CPUs from Python efficiently. The motivating case was scientific and AI inter-operator parallelism: lots of small Python steps between native kernels.
Sam Gross, who wrote PEP 703, said in a EuroPython keynote that only one thread can run Python code at a time. NumPy, PyTorch, and scikit-learn already hide parallelism inside a call by releasing the lock around work that does not touch Python objects.
The remaining pain is Python itself: worker threads that still have to run bytecode, or pipelines made of many small Python steps. If this lock is your first look at CPython internals, start with Python basics before you chase cores.
Free-threaded CPython is a separate binary. Installing Python still defaults to the normal build. Prefer the name free-threaded over “nogil”.
Thomas Wouters told PyCon US 2026 the Steering Council rejected “no-GIL” so they would not have to talk about a “no no-GIL build.”
Phase | Meaning | When |
|---|---|---|
I | Experimental build-time option. Single-thread tax 20–40% | 3.13 |
II | Officially supported, still optional. Single-thread tax about 5–10% | 3.14 (PEP 779, 16 Jun 2025) |
III | Free-threaded default | Has not happened |
3.13 shipped --disable-gil as an experiment. 3.14 is Phase II: supported, still optional. Phase III (default) needs a future PEP.
Charlie Marsh reported the label change when 3.14 stabilized.
As of Python 3.14, the free-threaded (or no-GIL) version of the Python interpreter is no longer considered experimental. https://t.co/AeY7IZFTPU
Official macOS and Windows installers expose a checkbox. From Astral, the uv path is a t suffix on the version:
uvx python@3.14tThat is the uv toolchain path for pins and tools. Identify the build with python -VV (the version string contains “free-threading build”), sysconfig.get_config_var("Py_GIL_DISABLED") == 1, and the runtime check:
import sys
print(sys._is_gil_enabled())False means the process is actually running without the lock. True on a 3.14t binary means something turned it back on.
Free-threaded builds can re-enable the GIL at runtime with PYTHON_GIL or -X gil. -X gil=0 / PYTHON_GIL=0 force it off. Unmarked C-API modules re-enable the GIL for the whole process and print a warning.
A PyO3 module declared with gil_used = true re-enables the GIL, and the interpreter prints a RuntimeWarning. Always re-check sys._is_gil_enabled() after imports. Wouters called forcing the GIL off with an unmarked extension playing with fire, something you probably only want for testing.
Dict, list, and set take internal locks that behave like the GIL for those types. That is not a language guarantee. Use threading.Lock for anything you share.
Do not read another thread’s frame.f_locals. Iterators are not concurrent-safe. ContextVars thread_inherit_context defaults true on free-threaded builds and false on the GIL build.
Subinterpreters remain the other path while the default build keeps the GIL. 3.14’s InterpreterPoolExecutor is shared-nothing, one GIL per interpreter, and uses fewer system resources than processes. On r/programming, u/vk6_ (Oct 2025) reported about 30% RAM savings moving a web server from multiprocessing to one-thread-per-core subinterpreters, without taking the free-threaded build.
Free-threading is worth it when the work is CPU-bound Python on independent data and C extensions have opted in. Single-thread scripts, Django-style request handlers that do not parallelize, and anything that mutates shared Python objects pay a tax. I/O-bound asyncio on the default GIL build already releases the lock, so free-threading buys nothing there.
Optiver draws the same split: free-threading for CPU-parallel work, async for I/O concurrency. asyncio.to_thread() is still GIL-bound for CPU on the default build. Asyncio on free-threaded Python wants one event loop per thread, not a shared loop.
Miguel Grinberg (8 Oct 2025) ran pure-Python benchmarks on 3.14 and 3.14t, with four threads each doing independent work:
Benchmark (seconds, Linux / macOS) | GIL 1T | GIL 4T | FT 1T | FT 4T |
|---|---|---|---|---|
Fibonacci, | 6.59 / 6.39 | 32.60 / 24.96 | 7.05 / 7.27 | 10.80 / 7.81 |
Bubble sort, 10,000 numbers | 2.18 / 2.05 | 10.55 / 8.27 | 2.66 / 2.28 | 6.23 / 3.02 |
Four Fibonacci threads ran about 3.1× faster on the free-threaded build, and bubble sort about 2×. On the GIL build, four threads took four to five times as long as one: zero multi-thread gain.
arXiv 2603.04782 (CPython 3.14.2) saw up to 4× time and energy on parallel independent data. Sequential runs used 13–43% more energy with no speedup. Shared-object mutation reduced the gains or reversed them.
Energy tracked wall time. Memory went up with per-object locks and a new allocator.
Sam Gross’s Hanabi trainer is the research-workload analog. With the GIL on, extra workers added no games per second. After removing it and sharding a pybind11 table, the system hit nearly 1,200 games/sec at 60 workers, about 20× versus the GIL build.
On r/Python, u/phovos (Nov 2025) posted a 14-queen bench via uv run --python python3.14t: GIL-enabled about 7.07s–7.51s from 1–8 threads; free-threaded 7.62s down to 1.82s at 8 threads. Textbook CPU-bound pure Python. Not how NumPy-class libraries work.
Quansight measured an asyncio TCP benchmark at 532 MB/s on the default build versus 1,455 MB/s free-threaded with 6 workers, and 698 versus 1,924 MB/s with 12, using one event loop per thread.
The free-threading howto measures pyperformance at about 1% on macOS aarch64 to 8% on x86-64 Linux. What’s New 3.14 says roughly 5–10%. PEP 779 cites about 10% on Linux/Windows, about 3% on macOS, with a hard ceiling of 15%.
Grinberg’s 3.14 free-threaded fibo ran at 91% of standard (7.05s vs 6.59s on Linux). That is fibo, not pyperformance, so keep it separate from the howto’s 1–8%.
3.13’s tax was a different world: the Language Summit put it at 40%, and Grinberg’s 3.13 fibo ran about 50% slower free-threaded. Those figures do not describe 3.14.
Memory is the other bill. PEP 779 reports a 15–20% higher geo-mean on pyperformance. Object headers are larger (None is 32 bytes versus 16 on AMD64).
On r/learnpython, u/latkde (Apr 2026) answers “should I use free threading” with no, except for CPU-bound multithreaded programs.
u/marr75 (Nov 2025) noted the other side: by count of programs, most Python has no parallel code. Compute already lives in torch, BLAS, DuckDB, or a web server.
The benches hold for CPU-bound independent Python. Use the workload as the filter, not the headline.
Native libraries that already release the GIL (Polars, NumPy, PyTorch, hashlib-in-C) do not need 3.14t to use extra cores inside one call. Multiprocessing still wins when you want crash isolation, a separate address space, or extensions that have not shipped t wheels.
Nathan Goldbaum reported on 11 Mar 2026 that 180 of the 360 most-downloaded packages with compiled wheels ship t wheels. Quansight’s guide tracks which packages are ready. The June 2025 Summit figure of about one in six is stale.
The live blocker for free-threading is wheels, not the interpreter flag. Import an unmarked C extension and the process re-enables the GIL. Check sys._is_gil_enabled() after the import graph settles.
Contended mutation of shared Python objects reduced speedups or reversed them in arXiv 2603.04782. Free-threaded dict, list, and set locks behave like the old GIL for those types only. The thread-safety docs spell out which operations still need your own locking.
On r/programming, u/frenchtoaster (Oct 2025) flagged the NumPy case: mutating an object concurrently from two threads is unsafe C. Fine for a local script. Unsafe for a web service that assumed the GIL’s accidental atomicity.
Default 3.14 and 3.14t are both real. Pablo Galindo Salgado argued in July 2023 against a permanent GIL / no-GIL split. Phase III has not landed, so you are still choosing a binary, an ABI, and a wheel tag.

Streamlit turns a Python script into a data app by rerunning the whole file. Covers session state, cache, Community Cloud caps, and when FastAPI wins.

Python wins data, ML, and automation; JavaScript wins the browser, and Node.js wins I/O-heavy APIs. If you can pick only one, pick the job.

A Python context manager is the with protocol: __enter__/__exit__, contextlib helpers, custom classes, and async with. Files are an example, not the definition.