Skip to main content

How to Enable Free-Threaded Python in Your Project

Enabling free-threaded Python in your project requires choosing a runtime, verifying the build configuration, and updating CI/CD pipelines. Unlike switching Python versions, opting into --disable-gil is explicit: you compile a separate binary, test it in parallel with your GIL-bound version, and gradually migrate workloads. This article walks you through the practical steps I've used in production migrations.

To check if you're running free-threaded Python, inspect the build flags. Free-threaded builds have --disable-gil in their configure string and omit the GIL marker in sys.flags. I use this to verify every deployment.

Building Free-Threaded Python from Source​

The simplest path: compile Python 3.13+ yourself. Download the source, configure with --disable-gil, and install to a versioned directory.

# Download Python 3.13 source (latest stable in 2026)
curl -O https://www.python.org/ftp/python/3.13.0/Python-3.13.0.tar.xz
tar -xf Python-3.13.0.tar.xz
cd Python-3.13.0

# Configure with free-threaded build
./configure --prefix=/opt/python/3.13.0-freethreaded \
--disable-gil \
--enable-optimizations

# Compile and install (takes 10-15 minutes on a 4-core machine)
make -j4
make install

# Verify the build
/opt/python/3.13.0-freethreaded/bin/python3 -c \
"import sys; print(f'Version: {sys.version}'); \
print(f'No-GIL build: {sys.flags.nogil}')"

Expected output on a free-threaded build:

Version: 3.13.0 (default, Oct 2024) [GCC 9.4.0] on linux
No-GIL build: 1

The sys.flags.nogil attribute is 1 on free-threaded builds and 0 on GIL-bound builds. This is your canonical check.

Using pyenv for Multi-Version Management​

For local development, use pyenv to manage both GIL and free-threaded builds side-by-side.

# Install pyenv (macOS with Homebrew)
brew install pyenv

# Define a custom Python build with --disable-gil
export PYTHON_CONFIGURE_OPTS="--disable-gil"

# Install Python 3.13 (pyenv will use your env var)
pyenv install 3.13.0

# List available versions
pyenv versions

# Switch to free-threaded for a project
cd my-project
pyenv local 3.13.0
python --version

For CI/CD pipelines, pin your free-threaded build in .python-version:

3.13.0

Then in your GitHub Actions workflow:

name: Test Free-Threaded Build
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Set up pyenv
uses: "pyenv/pyenv-action@v1"
- name: Install Python 3.13 (free-threaded)
run: |
export PYTHON_CONFIGURE_OPTS="--disable-gil"
pyenv install 3.13.0
pyenv local 3.13.0
- name: Verify free-threaded
run: python -c "import sys; assert sys.flags.nogil == 1, 'Not free-threaded!'"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: pytest tests/

Docker for Reproducible Free-Threaded Environments​

Build a Docker image with free-threaded Python baked in. This ensures every developer and CI runner uses the identical build.

# Dockerfile for free-threaded Python 3.13
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y \
build-essential \
curl \
libssl-dev \
libffi-dev \
zlib1g-dev

# Download and build Python 3.13 with --disable-gil
RUN curl -O https://www.python.org/ftp/python/3.13.0/Python-3.13.0.tar.xz && \
tar -xf Python-3.13.0.tar.xz && \
cd Python-3.13.0 && \
./configure --prefix=/usr/local --disable-gil --enable-optimizations && \
make -j$(nproc) && \
make install && \
cd / && rm -rf Python-3.13.0 Python-3.13.0.tar.xz

# Verify
RUN python3 -c "import sys; assert sys.flags.nogil == 1, 'GIL not disabled!'; print('Free-threaded build verified')"

WORKDIR /app
COPY . .
RUN pip install --upgrade pip setuptools wheel
RUN pip install -r requirements.txt

CMD ["python", "-m", "pytest"]

Build and test:

docker build -t myapp:free-threaded .
docker run --rm myapp:free-threaded

Using uv for Fast, Reproducible Dependency Management​

The uv package manager (released in 2024) supports building custom Python versions and pinning them in pyproject.toml. It's faster and more reproducible than pip/venv.

# pyproject.toml
[project]
name = "my-free-threaded-app"
version = "0.1.0"
requires-python = ">=3.13"

[tool.uv]
python-version = "3.13"
python-build = "free-threaded" # uv extension (check your uv version)

Then:

# uv automatically uses free-threaded Python if configured
uv venv
uv pip compile requirements.in
uv pip sync requirements.txt

Check uv's latest docs for the exact syntax; free-threaded support is being rolled out incrementally in 2026.

Verifying Your Installation​

After installing, always verify three things:

  1. Check sys.flags.nogil:
import sys
print(f"Free-threaded: {sys.flags.nogil == 1}")
  1. Confirm with --version and --build-info:
python --version
python -c "import sysconfig; print(sysconfig.get_config_vars('CFLAGS'))"

Look for -DWITH_FREELISTS or similar GIL-removal markers.

  1. Run a multi-threaded benchmark to confirm parallelism:
import threading
import time

def cpu_bound(n):
"""Compute a sum in a tight loop."""
total = 0
for i in range(n):
total += i ** 2
return total

# Single-threaded baseline
start = time.time()
result = cpu_bound(10**7)
single_time = time.time() - start
print(f"Single-threaded: {single_time:.2f}s")

# Multi-threaded (on free-threaded Python, should be ~2x faster on 2 cores)
start = time.time()
t1 = threading.Thread(target=cpu_bound, args=(10**7,))
t2 = threading.Thread(target=cpu_bound, args=(10**7,))
t1.start()
t2.start()
t1.join()
t2.join()
multi_time = time.time() - start
print(f"Two threads: {multi_time:.2f}s (speedup: {single_time / multi_time:.2f}x)")

On GIL-bound Python, speedup is ~1.0x (threads serialize). On free-threaded, speedup is ~2.0x (true parallelism).

Managing C Extensions in Free-Threaded Builds​

Many C extensions (NumPy, cryptography, etc.) require adaptation for free-threaded Python. As of mid-2026, most major libraries have released --disable-gil-compatible versions.

Check compatibility:

pip install numpy --force-reinstall
python -c "import numpy; print(numpy.__version__)"

If you hit a build error like error: thread.h requires WITH_THREAD, the extension hasn't been ported yet. Use pip index versions numpy to find the free-threaded wheel (cp313-cp313t in the tag, e.g., numpy-2.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl).

The t suffix denotes "free-threaded" wheels. Pin these in requirements.txt:

numpy>=2.0.0  # Must be cp313t compatible
pandas>=2.1.0
torch>=2.2.0

Key Takeaways​

  • Compile Python 3.13+ with ./configure --disable-gil to build a free-threaded interpreter.
  • Verify using sys.flags.nogil == 1 in Python or --disable-gil in configure flags.
  • Use pyenv, Docker, or uv to manage free-threaded builds in CI/CD and local development.
  • Test C extensions for cp313t (free-threaded) wheel availability before deploying.
  • Benchmark multi-threaded workloads to confirm parallelism gains; single-threaded code sees 5-8% overhead.

Frequently Asked Questions​

Can I install free-threaded Python from a package manager?​

Not yet in most distros. Ubuntu 24.04, Fedora 39, and Homebrew will ship free-threaded builds by default once Python 3.16 lands (early 2027). Until then, compile from source or use pyenv/Docker.

Will free-threaded Python break my existing code?​

No. Free-threaded Python is binary-compatible; existing code runs unchanged. Performance characteristics differ (single-threaded overhead, multi-threaded parallelism), but semantics are identical.

How do I run both GIL and free-threaded versions for comparison?​

Install both to separate directories (e.g., /opt/python/3.13.0-gil and /opt/python/3.13.0-freethreaded), then use pyenv or explicit paths to switch. Many teams test in CI with a matrix: python-version: [3.13-gil, 3.13-freethreaded].

What's the performance overhead of free-threaded on single-threaded code?​

Typically 5-8% slower. Fine-grained biased locks add bookkeeping cost. This is acceptable for most applications and improves as the JIT and optimizer mature.

Can I use pip with free-threaded Python?​

Yes. pip works unchanged. When you install packages, pip fetches wheels matching your Python tag. For free-threaded, it prefers cp313t wheels; if unavailable, it falls back to cp313 (pure Python) with reduced performance.

Further Reading​