Free-threaded Python (no GIL)

threading & multiprocessing · Expert · 13 min

Free-threading is the biggest change to Python concurrency in decades and affects how you write threaded code.

PEP 703 makes the GIL optional. Python 3.13 ships an experimental free-threaded build, usually installed as python3.13t (selectable in the official Windows and macOS installers, or built with --disable-gil). In 3.14 it becomes officially supported but still not the default.

In this build, threads execute Python bytecode in parallel on multiple cores, so CPU-bound pure-Python code can scale with threads, with no pickling and shared memory for free.

How to tell what you're running:

  • sys._is_gil_enabled() (3.13+) returns False when the GIL is actually off.
  • sysconfig.get_config_var("Py_GIL_DISABLED") is 1 on a free-threaded build.

Caveats:

  • Extensions must declare support. Importing one that doesn't re-enables the GIL at runtime (with a warning); PYTHON_GIL=0 or -X gil=0 forces it off.
  • Single-thread speed is lower in 3.13 (about 40% overhead on the pyperformance suite), largely because the specialising interpreter is disabled; 3.14 narrows the gap considerably.
  • Thread-safety: built-in types stay internally consistent using per-object locks, but races in your logic become much more frequent. Code that was "safe by accident" under the GIL will break. Use locks and queues properly.

Measure your own workload before switching.

Example

import sys, sysconfig, threading, time

free_build = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))
gil_on = sys._is_gil_enabled() if hasattr(sys, "_is_gil_enabled") else True
print(f"free-threaded build: {free_build}, GIL enabled now: {gil_on}")
# standard build: free-threaded build: False, GIL enabled now: True
# python3.13t:    free-threaded build: True, GIL enabled now: False

def cpu(n=3_000_000):
    total = 0
    for i in range(n):
        total += i
    return total

def run(threads):
    t = time.perf_counter()
    ts = [threading.Thread(target=cpu) for _ in range(threads)]
    for x in ts: x.start()
    for x in ts: x.join()
    return time.perf_counter() - t

one, four = run(1), run(4)
print(f"4 threads take {four / one:.1f}x the time of 1")
# with the GIL: ~4x (serialised); free-threaded on 4+ cores: ~1x (parallel)

Remember

  • Python 3.13 has an experimental free-threaded build (python3.13t)
  • Check with sys._is_gil_enabled() and the Py_GIL_DISABLED config var
  • Expect slower single-thread code, extension caveats and more exposed races

Watch out for

  • Assuming existing threaded code is correct on the free-threaded build because it never failed under the GIL.

Check yourself

  1. On python3.13t you import a C extension that hasn't declared free-threading support. What happens by default?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/howto/free-threading-python.html

Builds on

Unlocks

Nothing builds on this yet.