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.
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étodo | Tempo | Speedup |
|---|---|---|
| Sequencial | 12.4s | 1.0x |
| ThreadPoolExecutor (4 workers) | 12.8s | 0.97x |
| ProcessPoolExecutor (4 workers) | 3.5s | 3.5x |
Python 3.13t (sem GIL — free-threaded):
| Método | Tempo | Speedup |
|---|---|---|
| Sequencial | 13.1s | 1.0x |
| ThreadPoolExecutor (4 workers) | 3.6s | 3.6x |
| ProcessPoolExecutor (4 workers) | 3.7s | 3.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ério | Free-threading | Multiprocessing |
|---|---|---|
| Memória compartilhada | Sim (nativo) | Não (precisa de SharedMemory) |
| Overhead de criação | Baixo | Alto |
| Compatibilidade | Experimental | Estável |
| Comunicação entre workers | Variáveis compartilhadas | Pipes, Queues |
| Isolamento | Baixo | Alto |
| Ideal para | Dados compartilhados grandes | Tarefas independentes |
O futuro do Python sem GIL
O PEP 703 que introduziu o modo free-threaded estabelece um roadmap claro:
- Python 3.13 (atual): Modo experimental, opt-in, build separada
- Python 3.14-3.15: Estabilização, mais bibliotecas compatíveis
- 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.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português