Python 3.13 Sem GIL: Modo Free-Threaded — 2026 | Python Brasil

Entenda o modo free-threaded do Python 3.13 sem GIL. Benchmarks, exemplos práticos com threading e o futuro do paralelismo em Python.

8 min de leitura Equipe Python Brasil

Uma das maiores mudanças na história recente do Python chegou com o Python 3.13: o modo free-threaded (também chamado de no-GIL). Pela primeira vez, é possível rodar Python sem o famigerado Global Interpreter Lock, permitindo que múltiplas threads executem código Python em paralelo de verdade.

Neste artigo, vamos entender o que isso significa na prática, como habilitar o modo free-threaded, ver benchmarks comparativos e explorar o que muda para desenvolvedores Python no dia a dia.

O que é o GIL e por que ele era um problema?

O GIL (Global Interpreter Lock) é um mutex que protege o acesso a objetos Python, garantindo que apenas uma thread execute bytecode Python por vez. Ele existe desde os primórdios do Python e foi uma decisão de design que simplificou o gerenciamento de memória.

O problema é que o GIL impede paralelismo real em código CPU-bound com threads:

import threading
import time

def calcular_pesado(n):
    """Simula trabalho CPU-bound"""
    total = 0
    for i in range(n):
        total += i * i
    return total

# Com o GIL, essas threads executam UMA POR VEZ
inicio = time.perf_counter()

threads = []
for _ in range(4):
    t = threading.Thread(target=calcular_pesado, args=(10_000_000,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

fim = time.perf_counter()
print(f"Tempo com 4 threads: {fim - inicio:.2f}s")

No Python tradicional (com GIL), rodar esse código com 4 threads não é mais rápido do que rodar sequencialmente — e às vezes é até mais lento por causa do overhead de troca de contexto entre threads.

Quando o GIL NÃO era problema

É importante notar que o GIL não afeta operações de I/O. Quando uma thread está esperando uma resposta de rede, leitura de disco ou qualquer operação de I/O, ela libera o GIL automaticamente. Por isso, o módulo asyncio e bibliotecas como aiohttp funcionam bem mesmo com o GIL — confira nosso artigo sobre Python async/await para mais detalhes.

O GIL era problemático especificamente para:

  • Processamento numérico pesado
  • Manipulação de imagens e vídeo
  • Criptografia e hashing
  • Simulações e cálculos científicos
  • Qualquer operação CPU-bound que se beneficiaria de paralelismo real

Como habilitar o modo free-threaded

O Python 3.13 introduziu o modo free-threaded como experimental. Ele não vem habilitado por padrão — você precisa instalar uma build especial:

Instalação

# No Ubuntu/Debian — compilar do código-fonte com a flag
./configure --disable-gil
make -j$(nproc)
sudo make install

# O executável free-threaded tem o sufixo "t"
python3.13t --version

# Usando pyenv
pyenv install 3.13t

# Usando UV (recomendado)
uv python install 3.13t
uv venv --python 3.13t

Verificando se o GIL está desativado

import sys

# Verificar se está no modo free-threaded
print(sys._is_gil_enabled())  # False = GIL desativado

# Informações sobre a build
print(sys.version)
# Deve incluir algo como "experimental free-threading build"

O modo free-threaded pode ser controlado em tempo de execução:

# Desativar o GIL (padrão na build free-threaded)
PYTHON_GIL=0 python3.13t script.py

# Forçar o GIL mesmo na build free-threaded
PYTHON_GIL=1 python3.13t script.py

Benchmarks: antes e depois

Vamos comparar o desempenho de código CPU-bound com e sem o GIL usando concurrent.futures:

import concurrent.futures
import time
import sys

def cpu_intensivo(n):
    """Calcula a soma de quadrados até n"""
    total = 0
    for i in range(n):
        total += i * i
    return total

def benchmark_sequencial(tarefas, n):
    inicio = time.perf_counter()
    resultados = [cpu_intensivo(n) for _ in range(tarefas)]
    fim = time.perf_counter()
    return fim - inicio

def benchmark_threads(tarefas, n, workers):
    inicio = time.perf_counter()
    with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
        futuros = [executor.submit(cpu_intensivo, n) for _ in range(tarefas)]
        resultados = [f.result() for f in futuros]
    fim = time.perf_counter()
    return fim - inicio

def benchmark_processos(tarefas, n, workers):
    inicio = time.perf_counter()
    with concurrent.futures.ProcessPoolExecutor(max_workers=workers) as executor:
        futuros = [executor.submit(cpu_intensivo, n) for _ in range(tarefas)]
        resultados = [f.result() for f in futuros]
    fim = time.perf_counter()
    return fim - inicio

if __name__ == "__main__":
    TAREFAS = 8
    N = 5_000_000
    WORKERS = 4

    gil_status = "desativado" if not sys._is_gil_enabled() else "ativado"
    print(f"Python {sys.version}")
    print(f"GIL: {gil_status}")
    print(f"Tarefas: {TAREFAS}, Workers: {WORKERS}\n")

    t_seq = benchmark_sequencial(TAREFAS, N)
    print(f"Sequencial:      {t_seq:.2f}s")

    t_threads = benchmark_threads(TAREFAS, N, WORKERS)
    print(f"ThreadPool:      {t_threads:.2f}s (speedup: {t_seq/t_threads:.1f}x)")

    t_proc = benchmark_processos(TAREFAS, N, WORKERS)
    print(f"ProcessPool:     {t_proc:.2f}s (speedup: {t_seq/t_proc:.1f}x)")

Resultados típicos em uma máquina com 4 cores

Python 3.13 (com GIL):

MétodoTempoSpeedup
Sequencial12.4s1.0x
ThreadPoolExecutor (4 workers)12.8s0.97x
ProcessPoolExecutor (4 workers)3.5s3.5x

Python 3.13t (sem GIL — free-threaded):

MétodoTempoSpeedup
Sequencial13.1s1.0x
ThreadPoolExecutor (4 workers)3.6s3.6x
ProcessPoolExecutor (4 workers)3.7s3.5x

A diferença é clara: com o GIL, threads não oferecem nenhum ganho para código CPU-bound. Sem o GIL, threads atingem speedup quase linear — comparável ao multiprocessing, mas com muito menos overhead de memória.

Exemplo prático: processamento de imagens

Um caso de uso real onde o free-threading brilha é no processamento de imagens:

import concurrent.futures
from pathlib import Path
import sys

def processar_imagem(caminho):
    """Simula processamento pesado de uma imagem"""
    # Em um caso real, aqui teria operações com Pillow/OpenCV
    dados = Path(caminho).read_bytes()

    # Simula processamento CPU-bound
    total = 0
    for byte in dados:
        total += byte * byte

    return len(dados), total

def processar_lote(caminhos):
    """Processa um lote de imagens usando threads"""
    with concurrent.futures.ThreadPoolExecutor() as executor:
        resultados = list(executor.map(processar_imagem, caminhos))
    return resultados

# No Python 3.13t, cada thread realmente roda em um core separado
# Sem overhead de serialização/desserialização do multiprocessing

A vantagem do threading sobre multiprocessing nesse cenário é significativa:

  • Memória compartilhada: threads acessam os mesmos dados sem cópia
  • Sem overhead de IPC: não precisa serializar/desserializar objetos
  • Startup mais rápido: criar uma thread é muito mais leve que criar um processo
  • Comunicação simples: variáveis compartilhadas em vez de pipes/queues

Para se aprofundar em multiprocessing, confira nosso artigo sobre Python multiprocessing.

Cuidados e limitações

O modo free-threaded ainda é experimental e tem pontos de atenção importantes:

1. Compatibilidade de bibliotecas

Muitas extensões C do Python dependem do GIL para thread safety. Sem o GIL, elas podem apresentar comportamentos inesperados:

# Verificar se uma extensão é compatível com free-threading
import importlib.metadata

# Algumas bibliotecas já são compatíveis:
# - NumPy (a partir da versão 2.1)
# - Cython (a partir da 3.1)
# - pydantic (core em Rust, já thread-safe)

# Outras ainda estão em adaptação:
# - Algumas extensões C legadas
# - Bibliotecas que usam estado global mutável

2. Overhead no modo single-thread

A build free-threaded tem um overhead de 5-10% em código single-thread comparado à build padrão. Isso acontece porque operações atômicas e sincronização fina substituem o GIL global:

# O custo do free-threading em single-thread
# Python 3.13 (com GIL):    ~1.00x (referência)
# Python 3.13t (sem GIL):   ~1.05-1.10x (5-10% mais lento)

# Mas com múltiplas threads CPU-bound:
# Python 3.13 (com GIL):    ~1.00x (sem ganho)
# Python 3.13t (sem GIL):   ~0.25x com 4 threads (4x mais rápido)

3. Condições de corrida

Sem o GIL, você precisa se preocupar com thread safety no seu próprio código:

import threading

# PERIGOSO sem o GIL - condição de corrida!
contador = 0

def incrementar():
    global contador
    for _ in range(1_000_000):
        contador += 1  # Não é atômico!

# CORRETO - usando Lock
lock = threading.Lock()
contador_seguro = 0

def incrementar_seguro():
    global contador_seguro
    for _ in range(1_000_000):
        with lock:
            contador_seguro += 1

# MELHOR - evitar estado compartilhado quando possível
def somar_parcial(n):
    """Cada thread calcula sua parte independentemente"""
    total_local = 0
    for _ in range(n):
        total_local += 1
    return total_local

# Coletar resultados no final
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    parciais = list(executor.map(somar_parcial, [250_000] * 4))
    total = sum(parciais)

4. Quando NÃO usar free-threading

O modo free-threaded não é necessário quando:

  • Seu código é I/O-bound — use asyncio (veja nosso artigo sobre async/await)
  • Você precisa de máxima compatibilidade — muitas bibliotecas ainda não foram adaptadas
  • Seu código é single-threaded — o overhead não compensa
  • Você já usa multiprocessing com sucesso (veja nosso guia de multiprocessing)

Free-threading vs multiprocessing: quando usar cada um

CritérioFree-threadingMultiprocessing
Memória compartilhadaSim (nativo)Não (precisa de SharedMemory)
Overhead de criaçãoBaixoAlto
CompatibilidadeExperimentalEstável
Comunicação entre workersVariáveis compartilhadasPipes, Queues
IsolamentoBaixoAlto
Ideal paraDados compartilhados grandesTarefas independentes

O futuro do Python sem GIL

O PEP 703 que introduziu o modo free-threaded estabelece um roadmap claro:

  1. Python 3.13 (atual): Modo experimental, opt-in, build separada
  2. Python 3.14-3.15: Estabilização, mais bibliotecas compatíveis
  3. Python 3.16+: Possível tornar free-threading o padrão

A comunidade Python está trabalhando ativamente na adaptação de bibliotecas populares. NumPy, Cython e várias outras já são compatíveis ou estão no caminho. A tendência é que em 2-3 anos, a maioria do ecossistema esteja pronta para um Python sem GIL.

Conclusão

O modo free-threaded do Python 3.13 é uma mudança histórica que finalmente resolve uma das maiores limitações da linguagem. Para desenvolvedores que trabalham com processamento pesado, isso significa poder usar threads de verdade para paralelismo, com menos overhead que multiprocessing e sem a complexidade de bibliotecas como multiprocessing.shared_memory.

Porém, é importante lembrar que o recurso ainda é experimental. Para produção, avalie cuidadosamente a compatibilidade das suas dependências antes de migrar. Para projetos novos e experimentação, é uma excelente oportunidade de explorar o que o futuro do Python reserva.

Se você quer se aprofundar em concorrência no Python, confira nossos artigos sobre async/await, multiprocessing e boas práticas Python em 2026. Para entender melhor o ecossistema de ferramentas modernas, veja também nosso post sobre tipagem estática com mypy.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português