Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 14 – Concurrency and parallelism: threads, processes, interpreters

Running Python without the GIL (3.14t builds)

Install and detect a free-threaded CPython 3.14t build, measure what the GIL costs your threads, and keep code correct when they run on several cores.

  • Advanced
  • 24 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 9 of 24 minutes free
Free: Two builds of Python 3.14 · Which build am I running? · What the GIL costs threads, measured · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 15 minutes.

What you will learn

  • Install a free-threaded build of Python 3.14 and detect which build is running
  • Measure how many cores CPU-bound threads keep busy while the GIL is on
  • Check whether a setting or an extension module turned the GIL back on
  • Write thread code that stays correct with the GIL and without it

Before you start

What you’ll learn · Before you start

What you will learn

  • Install a free-threaded build of Python 3.14 and detect which build is running
  • Measure how many cores CPU-bound threads keep busy while the GIL is on
  • Check whether a setting or an extension module turned the GIL back on
  • Write thread code that stays correct with the GIL and without it

Before you start

On this page

A free-threaded build of CPython is a separate interpreter, installed as python3.14t beside the usual python3.14, in which the global interpreter lock can be switched off. Without the GIL, the threads of one process run Python code on several cores at the same moment, so CPU-bound work can speed up with threads instead of processes. Python 3.14 is the first release in which this build is officially supported rather than experimental (PEP 779), and it is still optional: the default build keeps its GIL. Three costs come with it. Single-threaded code runs a little slower. An extension module that has not declared support for running without the GIL turns it back on for the whole process. And code that relied on the GIL to make several steps happen together needs locks of its own, on either build.

Two builds of Python 3.14

Default build: four threads share one GIL. Free-threaded build: they run on separate cores until an old extension brings it back.Default buildpython3.14Free-threaded buildpython3.14t4 threadsOne GIL:one thread at a timeruns Python code4 threadsNo GIL:each thread runson its own coreImport of anundeclared extensionGIL back on,with a warningtake turnsat onceuntila separateinterpreter

The default build and the free-threaded build of Python 3.14

Text description of the diagram

The diagram has two boxes, one above the other, joined by an arrow that says the free-threaded build is a separate interpreter.

  • The top box is the default build, run as python3.14. Its four threads take turns holding one GIL, so only one of them runs Python code at any moment.
  • The bottom box is the free-threaded build, run as python3.14t. Its four threads run Python code at the same time, each on its own core, because there is no GIL.
  • Below that, still in the bottom box: this lasts until the program imports an extension module that has not declared it can run without the GIL. Python then turns the GIL back on for the whole process and prints a warning.
The default and the free-threaded build of CPython 3.14
Criterion Default buildFree-threaded build
Command python3.14python3.14t
Threads run Python code in parallel no: one GILyes, while the GIL is off
Is the GIL on? alwaysonly if something turned it on
Context of a new thread emptya copy of its creator's
Extension modules allthose built for it; others turn the GIL on
When to choose Most programs todayCPU-bound threads whose extensions support it

The two builds share the language and the standard library, but not compiled extension modules: a package with C code needs a wheel built for the free-threaded build. PEP 703 marks such builds with the letter t in their ABI tag, which is where sys.abiflags and the t in python3.14t come from. What’s New in Python 3.14 puts the cost at roughly 5 to 10 percent on single-threaded code, and the free-threading guide, measured with the pyperformance suite, at about 1 percent on macOS on Apple silicon to 8 percent on x86-64 Linux.

Which build am I running?

This file reports both things that matter: how the interpreter was built, and whether the GIL is on at this moment. It runs in your browser too, where it describes the browser’s default build:

The build, the GIL and two defaults that differ between builds Python · which_build.py
"""Which build of CPython runs this file, and is the GIL on right now?"""
import sys
import sysconfig

print("free-threaded build:", sysconfig.get_config_var("Py_GIL_DISABLED") == 1)
print("'free-threading build' in sys.version:", "free-threading build" in sys.version)
print("ABI flags:", repr(getattr(sys, "abiflags", "")))  # contains 't' on a free-threaded build (Linux, macOS)
print("GIL enabled right now:", sys._is_gil_enabled())
print("new threads inherit the caller's context:", bool(sys.flags.thread_inherit_context))
print("catch_warnings() uses a context variable:", bool(sys.flags.context_aware_warnings))

Output

free-threaded build: False
'free-threading build' in sys.version: False
ABI flags: ''
GIL enabled right now: True
new threads inherit the caller's context: False
catch_warnings() uses a context variable: False

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 which_build.py

sysconfig.get_config_var("Py_GIL_DISABLED") == 1 is the check the free-threading guide recommends for decisions about the build, and "free-threading build" in sys.version says the same in words. sys._is_gil_enabled() answers a different question, whether the GIL is on right now, which on a free-threaded build can change while the program runs. The last two lines are defaults that the free-threaded build changes, explained further down.

To get the free-threaded build on Windows, run py install 3.14t with the Python install manager. On macOS, the python.org installer has a Free-threaded Python package under Customize, which adds python3.14t. On Linux, use a distribution package if yours has one, or build CPython from source with ./configure --disable-gil.

What the GIL costs threads, measured

The same CPU-bound work, 32 pieces of one loop, runs on eight threads and then on eight processes. Each piece measures its own CPU time, so the output can say how many cores each pool kept busy at once:

One workload on eight threads and on eight processes Python · threads_or_processes.py
"""The same CPU-bound work on 8 threads and on 8 processes: the processes show how many cores the computer
could give, the threads how many the GIL allowed them to use."""
import sys
import sysconfig
import time
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

WORKERS = 8
PIECES = 32
SIZE = 1_200_000  # loop iterations per piece


def work(n):
    """A pure-Python loop; returns the CPU time it took, measured in the worker that ran it."""
    began = time.thread_time()
    total = 0
    for i in range(n):
        total += i * i % 7
    return time.thread_time() - began


def cores_busy(label, executor_class):
    with executor_class(max_workers=WORKERS) as pool:
        list(pool.map(work, [1] * WORKERS))  # start every worker before the clock starts
        began = time.perf_counter()
        cpu = sum(pool.map(work, [SIZE] * PIECES))
        wall = time.perf_counter() - began
    print(f"{label}: {cpu:.2f} s of CPU in {wall:.2f} s, cores busy at once: {cpu / wall:.1f}")
    return cpu / wall


if __name__ == "__main__":
    free = sysconfig.get_config_var("Py_GIL_DISABLED") == 1
    print(f"free-threaded build: {free}, GIL enabled: {sys._is_gil_enabled()}")
    threads = cores_busy("8 threads  ", ThreadPoolExecutor)
    processes = cores_busy("8 processes", ProcessPoolExecutor)
    if threads < 1.2:
        print("verdict: the threads stayed within one core; the processes show there were more to use")
    else:
        print("verdict: the threads ran Python code on several cores at once, as the processes did")

Output

free-threaded build: False, GIL enabled: True
8 threads  : 2.50 s of CPU in 3.04 s, cores busy at once: 0.8
8 processes: 2.37 s of CPU in 1.05 s, cores busy at once: 2.3
verdict: the threads stayed within one core; the processes show there were more to use

This output changes from run to run: the times depend on the computer and on what else it runs at the same moment; on the default build the verdict does not

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 threads_or_processes.py

Both pools did the same CPU work, about 2.4 seconds of it. The processes show how many cores the computer could give at that moment, here 2.3 on a computer busy with other programs, and finished in 1.05 seconds. The threads needed 3.04 seconds and kept 0.8 cores busy, below one, because with the GIL on only one of them runs Python code at a time, however many cores are idle. Run the same file with python3.14t and the threads’ figure rises to meet the processes’, since each thread then runs on a core of its own.

What changes for your code

Built-in types such as dict, list and set protect each of their own operations with internal locks, so one append() or one d[key] = value is as safe as with the GIL. Anything made of several steps is not: a check followed by an update, counter += 1, moving an item between two lists. Those already needed a lock on the default build, and without the GIL their races happen far more often. The free-threading guide also warns that sharing one iterator between threads is not safe, and that a frame’s local variables must not be read from another thread while it runs.

A few switches and habits decide how a program behaves on each build. PYTHON_GIL=1 or -X gil=1 turns the GIL on in a free-threaded build, and PYTHON_GIL=0 or -X gil=0 keeps it off. The flag thread_inherit_context gives each new thread a copy of its creator’s context. A start-up check tells a program whether its threads will run in parallel. And a lock around next() makes one iterator safe to share.

Check yourself

Four questions on the free-threaded build

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 A program must know whether the GIL is enabled in its own process at this moment. What does it call?

    Choose one answer.

    Show the answer to question 1

    Answer: sys._is_gil_enabled()

    sys._is_gil_enabled() reports the state of the running process, which can differ from the build: a free-threaded build turns the GIL back on for PYTHON_GIL=1, for -X gil=1, or when it imports an extension module that has not declared support. The other two checks describe how Python was built, not what it is doing now.

  2. Question 2 of 4 Which check does the free-threading guide recommend for decisions about how Python was built?

    Choose one answer.

    Show the answer to question 2

    Answer: The Py_GIL_DISABLED configuration variable, read with sysconfig.get_config_var(), is 1

    The configuration variable Py_GIL_DISABLED is 1 exactly when the build supports free threading, whatever the GIL is doing at the moment. Python 3.14 has both builds, so the version alone says nothing about it.

  3. Question 3 of 4 Code runs in several threads of a free-threaded build. Which of these still needs a lock of your own?

    Choose one answer.

    Show the answer to question 3

    Answer: if stock[item] > 0: stock[item] -= 1

    Built-in types protect each of their own operations with internal locks, so a single append, assignment or len() is safe. The stock check is two operations, a read and then a write; another thread can sell the last item between them, on either build.

  4. Question 4 of 4 A free-threaded build imports an extension module that has not declared it can run without the GIL. What happens, unless the GIL was forced off?

    Choose one answer.

    Show the answer to question 4

    Answer: The GIL is turned on again for the whole process, and Python prints a warning

    The free-threaded build cannot know whether such a module is safe, so it enables the GIL before continuing and warns, naming the module. PYTHON_GIL=0 or -X gil=0 keeps it off at your own risk.

The GIL switches, their failure on the default build and the changed defaults, reproduced Pro: opens with a Pro, Premium or Ultimate pass

Every switch that controls the GIL is run in this part, including the one that stops the default build before it starts, along with the changed default for new threads and a start-up check you can copy.

In this part:

  • Asking the default build to drop the GIL: the fatal error, reproduced
  • The context a new thread starts with, shown with decimal
  • A start-up check that says whether threads will run in parallel
  • Memory on the free-threaded build: what grows, and why

3 runnable examples (Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Code that is correct on both builds: iterators, locks and what they cost, measured Pro: opens with a Pro, Premium or Ultimate pass

Removing the GIL changes how often a race happens, not whether the code has one. This part reproduces a race that the default build shows too, fixes it with a lock, measures the lock, and ends with an exercise that is checked on both builds.

In this part:

  • One generator shared by two threads: the ValueError, reproduced
  • One lock around next(): every item exactly once, and what the lock costs
  • What the built-in types guarantee, and what still needs your lock
  • Measuring a switch to the free-threaded build, three runs
  • Exercise: a counter and a parallel sum that are correct on both builds
    • A counter and a parallel sum that are correct on both builds

2 runnable examples (Python) · 1 exercise with sample tests · about 5 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Mid to senior · Warm-up

    What is the difference between sys._is_gil_enabled() and the Py_GIL_DISABLED configuration variable, and when do you use each?

    They answer two different questions. The configuration variable Py_GIL_DISABLED, read with sysconfig.get_config_var(), describes how the interpreter was built: it is 1 for a free-threaded build such as python3.14t and 0 for the default build, and it never changes while the program runs. The free-threading guide recommends it for decisions about the build, such as which wheels or code paths to use. sys._is_gil_enabled() reports whether the GIL is on in this process right now. On the default build it is always True; on a free-threaded build it is False unless PYTHON_GIL=1, -X gil=1 or the import of an extension module that has not declared support turned the GIL back on. Check it after the program's imports, when you need to know whether threads will really run in parallel.

  2. Senior to staff

    A service was moved to python3.14t so that its 16 worker threads compute in parallel, but its CPU use never goes above one core, and a warning about the GIL appears once in the start-up logs. Diagnose it and describe the fix.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    Eight threads share one generator that yields job ids. On the default build a job occasionally fails with "ValueError: generator already executing"; on the free-threaded build some jobs seem to run twice. Explain both symptoms and fix the design.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    After switching a billing service to python3.14t, invoice totals computed in worker threads round differently, although no code changed. The threads are started inside a decimal.localcontext() block. What changed, and how do you make the service behave the same on both builds?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • The free-threaded build is a separate interpreter, python3.14t, officially supported from Python 3.14 and optional.
  • Check the build with sysconfig.get_config_var("Py_GIL_DISABLED") and the running state with sys._is_gil_enabled().
  • With the GIL on, CPU-bound threads keep at most one core busy; without it, they use several.
  • An extension module without declared support turns the GIL back on; PYTHON_GIL and -X gil override it.
  • Built-in types protect single operations; anything made of several steps still needs your own lock.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.