Estado Compartilhado e Limites de Isolamento
Python oferece threads, processos, concurrent.futures, subinterpreters e agora um build free-threaded - o que pode parecer uma caixa de ferramentas incomumente grande para um único problema.
Busque em todas as páginas da documentação
Python oferece threads, processos, concurrent.futures, subinterpreters e agora um build free-threaded - o que pode parecer uma caixa de ferramentas incomumente grande para um único problema.
Na verdade, é um pequeno número de ferramentas posicionadas em um espectro: quanta memória duas partes de código concorrente têm permissão para compartilhar, e qual mecanismo impede que essa memória compartilhada seja corrompida.
Cada ferramenta nesta seção é uma resposta específica para essa mesma pergunta, não um recurso não relacionado.
Fundamentos de Concorrência introduz a distinção concorrência versus paralelismo, e Escolhendo um Modelo de Concorrência fornece uma lista de verificação prática de decisão; threading, multiprocessing, O GIL e Python Free-Threaded e Subinterpreters cobrem cada ponto específico no espectro em profundidade.
Esta página é a camada abaixo de todas elas: o único eixo em que todas elas têm posições diferentes, e por que esse eixo é o que realmente decide qual ferramenta se adapta a uma carga de trabalho específica.
multiprocessing, executores concurrent.futures, subinterpreters, builds free-threaded do Python.Duas ou mais partes de código rodando "ao mesmo tempo" sempre têm que responder a uma pergunta fundamental: o que acontece se ambas tentarem ler ou modificar a mesma parte da memória de uma vez?
O modelo de concorrência de uma linguagem é, em sua essência, um conjunto de respostas para essa pergunta, e Python oferece várias porque cargas de trabalho diferentes querem respostas diferentes.
Em um extremo está o compartilhamento total com um único lock: uma thread roda dentro do mesmo processo que todas as outras threads, vê os mesmos objetos na memória, e no build CPython padrão, apenas uma thread executa bytecode Python a cada instante porque o Global Interpreter Lock (GIL) impõe essa regra.
No outro extremo está o nenhum compartilhamento: um processo (via multiprocessing) tem seu próprio espaço de memória privado, seu próprio interpretador e seu próprio GIL, então dois processos podem executar bytecode Python genuinamente simultaneamente em núcleos separados - o preço é que eles não veem automaticamente os objetos uns dos outros, e qualquer coisa passada entre eles tem que ser serializada (pickled) através do limite.
Uma analogia útil: threads são colegas de trabalho em um escritório compartilhado, capazes de alcançar o mesmo arquivo instantaneamente, mas apenas um de cada vez por regra da casa; processos são escritórios separados em prédios diferentes, cada um com seu próprio arquivo, capazes de trabalhar simultaneamente, mas precisando de um mensageiro (serialização) para trocar qualquer papelada.
# threads: mesma memória, mesmos objetos, um GIL - seguro por serialização, não isolamento
counter = {"n": 0}
# cada thread vê este dicionário exato; um lock é necessário para incrementá-lo com segurança
# processos: memória separada, objetos separados - seguro por isolamento, não por lock
# um processo filho recebe sua própria cópia; mudanças não aparecem no pai automaticamenteO GIL é o mecanismo que torna o modelo padrão do CPython, de memória totalmente compartilhada, seguro: ele garante que apenas uma thread execute bytecode Python por vez, o que significa que a maioria das operações únicas em objetos Python não falha ou corrompe sem um lock explícito, ao custo de threads nunca executarem código Python verdadeiramente em paralelo umas com as outras.
Esse único fato explica a divisão que esta seção continua a retornar: threads ajudam o trabalho intensivo de I/O, porque operações de I/O liberam o GIL enquanto esperam pela rede ou disco, permitindo que outra thread rode durante essa espera - mas threads não ajudam o trabalho intensivo de CPU, porque um loop Python apertado nunca libera o GIL, então N threads fazendo computação pura não rodam mais rápido que uma.
multiprocessing contorna o GIL inteiramente dando a cada worker seu próprio interpretador e seu próprio GIL, que é por que é a resposta padrão para paralelismo intensivo de CPU no CPython comum - o isolamento que torna a serialização necessária é o mesmo isolamento que torna a execução paralela real possível.
concurrent.futures não introduz um novo ponto neste espectro; é uma interface Executor uniforme (submit, map) sobre duas existentes - ThreadPoolExecutor para o lado da memória compartilhada, ProcessPoolExecutor para o lado isolado - então alternar entre elas é uma mudança de uma linha depois que o código é escrito contra a API Executor em vez de contra threads ou processos diretamente.
Memória compartilhada, um GIL Memória isolada, um GIL cada
------------------------- ------------------------------
threading.Thread multiprocessing.Process
ThreadPoolExecutor ProcessPoolExecutor
barato para iniciar, barato para compartilhar caro para iniciar, deve serializar para compartilhar
trabalho intensivo de I/O trabalho intensivo de CPU
Filas (queue.Queue para threads, multiprocessing.Queue para processos) existem porque "isolado" e "compartilhado" ainda precisam de uma maneira segura de passar dados através do limite - uma fila thread-safe coordena o acesso dentro da memória compartilhada, enquanto uma fila process-safe realmente serializa cada item através do limite do processo, fazendo mais trabalho por item precisamente porque não há memória para compartilhar em primeiro lugar.
Dois desenvolvimentos mais recentes no CPython relaxam o padrão "um GIL, totalmente compartilhado" sem se mover totalmente para processos separados, e ambos são melhor entendidos como novos pontos no mesmo espectro em vez de conceitos totalmente novos.
Subinterpreters (utilizáveis do Python via APIs relacionadas à PEP 734, 3.12+/3.13+) dão a cada interpretador seu próprio GIL e, por padrão, seu próprio estado isolado, enquanto ainda vivem dentro de um único processo OS - um ponto intermediário entre "um GIL compartilhado" e "processos totalmente separados", trocando parte do custo de inicialização e IPC do multiprocessing por um isolamento mais leve que um processo completo, ao custo de a maioria dos objetos ainda não ser diretamente compartilhável através do limite.
O CPython Free-Threaded (3.13+, experimental) adota a abordagem oposta: em vez de isolar a memória para evitar a necessidade de um único lock global, ele substitui o GIL único por locks mais granulares, por objeto, para que threads realmente possam executar bytecode Python em paralelo enquanto continuam a compartilhar memória diretamente - o mesmo modelo de memória compartilhada da thread comum, mas com a segurança anteriormente fornecida por um grande lock agora distribuída por muitos menores.
Essa única mudança é significativa o suficiente para justificar cautela real: extensões C escritas assumindo que um GIL as protegia implicitamente podem não ser seguras sob um build free-threaded, a menos que sejam atualizadas para declarar e impor sua própria thread-safety, que é por que adotar um build sem GIL em produção significa auditar a árvore de dependências, não apenas mudar uma flag de build.
| Modelo | Memória | Mecanismo de segurança | Melhor Ajuste |
|---|---|---|---|
| Threads (GIL padrão) | Totalmente compartilhada | Um GIL serializa a execução do bytecode | Trabalho intensivo de I/O; compartilhar objetos de forma barata é mais importante que paralelismo de CPU |
Processos (multiprocessing) | Isolado por worker | Limite de processo em nível de OS; nenhum estado compartilhado por padrão | Trabalho intensivo de CPU que necessita de execução paralela real entre núcleos |
| Subinterpreters | Isolado por interpretador, um processo | GIL por interpretador | Isolar código não confiável ou estilo plugin sem o overhead de um processo completo |
| Build Free-Threaded | Totalmente compartilhada | Locks por objeto em vez de um lock global | Cargas de trabalho com threads intensivas em CPU, uma vez que a árvore de dependências seja verificada como thread-safe |
O resultado prático é que "qual ferramenta de concorrência devo usar" é realmente "quanto isolamento essa carga de trabalho precisa, e quanto posso pagar por isso" - Escolhendo um Modelo de Concorrência transforma isso em uma lista de verificação concreta, mas a lista de verificação só faz sentido quando esse trade-off memória compartilhada versus isolamento é a lente pela qual você a está vendo.
concurrent.futures é um terceiro modelo de concorrência, separado de threads e processos." É uma interface, não um modelo - ThreadPoolExecutor e ProcessPoolExecutor são o mesmo trade-off threads vs. processos descrito aqui, embrulhado em uma API consistente.Quanta memória é compartilhada entre partes de código rodando concorrentemente, e qual mecanismo mantém essa memória compartilhada segura - compartilhamento total sob um lock, ou isolamento total sem necessidade de lock.
Segurança. Ele garante que apenas uma thread execute bytecode Python por vez, o que mantém as estruturas internas de objetos do CPython consistentes sem que cada operação precise de seu próprio lock explícito - o trade-off de performance (nenhum paralelismo de CPU entre threads) é o custo dessa garantia de segurança, não seu propósito.
Operações de I/O liberam o GIL enquanto esperam, permitindo que outra thread rode durante essa espera - um loop de computação Python apertado nunca atinge um ponto onde libera o GIL, então outras threads simplesmente esperam sua vez em vez de rodar em paralelo.
Porque cada processo tem seu próprio espaço de memória separado e seu próprio interpretador - não há memória compartilhada que um processo filho possa ler diretamente do pai, então qualquer dado que cruza esse limite tem que ser convertido para uma forma transportável e reconstruído do outro lado.
Não - é uma interface Executor uniforme sobre os mesmos dois modelos subjacentes. ThreadPoolExecutor usa threads e memória compartilhada; ProcessPoolExecutor usa processos e memória isolada; alternar entre eles é, na maioria das vezes, uma mudança de uma linha.
Eles oferecem isolamento semelhante a processos separados - cada um com seu próprio GIL e, por padrão, seu próprio estado - enquanto permanecem dentro de um único processo OS, o que é mais leve do que iniciar processos completos para cargas de trabalho que precisam principalmente de isolamento em vez de acesso a memória compartilhada.
Não - ele remove o único lock global e o substitui por locks mais granulares, por objeto, então o acesso genuinamente concorrente a dados compartilhados ainda é possível e o código (e extensões C) ainda precisa ser escrito para ser thread-safe sob esse modelo mais granular.
Processos são mais pesados para iniciar e requerem serialização de dados através do limite, o que adiciona overhead real - para trabalho intensivo de I/O, onde o GIL não é o gargalo em primeiro lugar, esse overhead não compra nada e threads (ou async) são o ajuste mais barato.
Funcionalmente semelhantes na superfície, mas mecanicamente diferentes: queue.Queue coordena o acesso seguro a objetos compartilhados na memória entre threads, enquanto multiprocessing.Queue realmente serializa cada item através do limite do processo, já que não há memória para compartilhar diretamente.
Não com segurança, sem auditoria prévia - extensões C escritas assumindo que o GIL as protegia implicitamente podem não ser thread-safe sob um build onde esse lock único não existe mais, então verificar a árvore de dependências é importante antes de confiar nele em produção.
Pergunte se a carga de trabalho é intensiva em I/O ou em CPU: trabalho intensivo de I/O se beneficia do compartilhamento barato das threads, já que o GIL não é a restrição, enquanto trabalho intensivo de CPU precisa do paralelismo real que apenas processos isolados (ou um build free-threaded verificado) podem fornecer.
Eles abordam a mesma limitação subjacente - um único GIL serializando tudo - de direções opostas: subinterpreters adicionam isolamento para contornar o compartilhamento do lock, enquanto free-threading remove o lock único, mas mantém a memória totalmente compartilhada.
Versões do Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.
Revisado por Chris St. John·Última atualização: 15 de jul. de 2026