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:
- Check
sys.flags.nogil:
import sys
print(f"Free-threaded: {sys.flags.nogil == 1}")
- Confirm with
--versionand--build-info:
python --version
python -c "import sysconfig; print(sysconfig.get_config_vars('CFLAGS'))"
Look for -DWITH_FREELISTS or similar GIL-removal markers.
- 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-gilto build a free-threaded interpreter. - Verify using
sys.flags.nogil == 1in Python or--disable-gilin configure flags. - Use
pyenv, Docker, oruvto 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.