GIL em Python

python
GIL significa Global Interpreter Lock (Trava Global do Interpretador). É um mutex que protege o acesso a objetos internos do Python. Primeiramente, o GIL permite que apenas uma thread execute bytecode por vez. Por exemplo, mesmo com 8 núcleos de CPU, apenas um executa Python puro. Isso limita severamente o paralelismo para código CPU-intensivo. Além disso, o GIL simplifica a gerência de memória do Python. A voz passiva é usada aqui: “objetos são protegidos contra corrupção pelo GIL”. Quando o GIL é um problema? Em tarefas que consomem muita CPU. Por exemplo, cálculos matemáticos, loops pesados ou processamento de imagens. Por outro lado, o GIL não atrapalha operações de I/O. Leitura de arquivos, requisições de rede ou acesso a banco são liberados. Portanto, Python com threads é excelente para I/O-bound. Vamos explorar na prática esses conceitos. Três subtítulos mostram quando o GIL atrapalha ou ajuda.

Gil prejudicando: cpu-bound com threads

Quando o código é intensivo em CPU, threads não ajudam. O GIL força as threads a executarem uma por vez. Portanto, o ganho de performance é zero ou negativo. Por exemplo, calcular números primos em várias threads não acelera. A voz passiva é aplicada: “o tempo total é similar ao sequencial”. Veja um exemplo prático dessa limitação: O resultado mostra aceleração próxima de 1.0. Ou seja, threads não trouxeram ganho para CPU. Para esses casos, use multiprocessing ou bibliotecas em C (NumPy).

Gil ajudando: i/o-bound com threads

Para operações de I/O, o GIL é liberado durante a espera. Enquanto uma thread aguarda dados da rede, outra executa. Portanto, threads trazem ganhos enormes para I/O-bound. Por exemplo, baixar 10 arquivos simultaneamente é muito mais rápido. A voz passiva é aplicada: “as operações de I/O são realizadas fora do GIL”. Exemplo demonstrando a eficiência para I/O: Threads reduzem o tempo total de 5 segundos para cerca de 1 segundo. Isso é o poder da concorrência para operações de I/O. A fórmula do ganho teórico é: \(G = \frac{T_{\text{sequencial}}}{T_{\text{thread}}} \approx N_{\text{tarefas}}\) Quando as tarefas são independentes e bloqueantes.

Alternativas para contornar o gil

Para CPU-bound, use multiprocessing em vez de threading. Cada processo tem seu próprio GIL e memória separada. Assim, você aproveita múltiplos núcleos de verdade. Outra alternativa é usar bibliotecas em C como NumPy. Elas liberam o GIL durante operações pesadas. Para I/O-bound, threading e asyncio são excelentes. A escolha certa depende do seu problema específico. A voz passiva é aplicada: “decisões informadas são tomadas após medição”. Exemplo de multiprocessing para contornar o GIL: Multiprocessing mostra aceleração real próxima ao número de núcleos. Threads para CPU-bound mostram aceleração próxima de 1.0. Entenda o GIL e escolha a ferramenta certa. Para I/O: threading ou asyncio. Para CPU: multiprocessing, NumPy ou Cython. O GIL não é um monstro, apenas uma característica. Aprenda a conviver com ele e seja feliz em Python.

Multithreading em Python

python

Multithreading permite múltiplas threads dentro de um único processo. Cada thread executa um fluxo independente de instruções. Primeiramente, threads compartilham a mesma memória do processo. Por exemplo, duas threads podem acessar a mesma variável global. Isso facilita a comunicação entre elas. Além disso, threads são mais leves que processos completos. A voz passiva é usada aqui: “as threads são gerenciadas pelo sistema operacional”. Quando utilizar multithreading em Python? Em operações de I/O. Por exemplo, downloads de rede, leitura de arquivos ou consultas a banco. No entanto, o GIL (Global Interpreter Lock) limita a execução paralela. Portanto, threads não aceleram código com CPU intensivo. Vamos explorar criação, sincronização e boas práticas. Três subtítulos guiarão você pelo mundo do threading. Ao final, você dominará a concorrência em Python.

Criando e gerenciando threads

Python oferece o módulo threading para trabalhar com threads. A classe Thread representa uma thread executável. Para criar uma thread, instancie Thread(target=funcao). Depois, chame start() para iniciar a execução. O método join() espera a thread terminar. Quando usar criação manual? Em scripts com poucas threads controladas. A voz passiva é aplicada: “os argumentos são passados via args“. Exemplo básico de criação de threads:

Threads executam concorrentemente, não em paralelo real. O GIL alterna entre elas rapidamente, dando a ilusão de simultaneidade. Para I/O, isso é suficiente e muito eficiente.

Sincronização entre threads

Quando threads compartilham dados, podem ocorrer condições de corrida. Por exemplo, duas threads incrementando a mesma variável simultaneamente. Isso corrompe o resultado final. Para evitar isso, use mecanismos de sincronização. O Lock (mutex) permite que apenas uma thread execute uma seção crítica. Outros mecanismos incluem RLock, Semaphore e Event. Quando usar locks? Sempre que múltiplas threads acessarem dados compartilhados. A voz passiva é usada aqui: “os recursos compartilhados são protegidos por locks”. Exemplo de contador seguro com lock:

O resultado sem lock será imprevisível e geralmente menor que 500000. Com lock, o resultado é sempre correto, porém mais lento. A fórmula do tempo de execução com contenção é: \(T = T_0 + C \times L\) Onde C é o número de aquisições e L é a latência do lock.

Threads vs. outras formas de concorrência

Threads são ótimas para I/O-bound com poucas conexões. Para CPU-bound, prefira multiprocessing (processos separados). Para milhares de conexões simultâneas, asyncio é superior. Quando escolher threading especificamente? Em projetos simples. Também quando você já tem código síncrono e não quer reescrever. Threads são mais fáceis de entender que asyncio. A desvantagem é o GIL e a complexidade de locks. A voz passiva é aplicada: “decisões de arquitetura são baseadas no tipo de tarefa”. Exemplo prático de pool de threads para tarefas I/O:

ThreadPoolExecutor simplifica o gerenciamento de threads. Ele reutiliza threads para evitar custo de criação. Use max_workers entre 5 e 10 para tarefas de rede. Nunca crie mais de 100 threads simultâneas. O sistema operacional pode ficar sobrecarregado. Threads são ferramentas poderosas quando usadas corretamente. Domine os locks e evite deadlocks. Sua aplicação ficará mais rápida e responsiva. Comece com pequenos exemplos e escale gradualmente.

LinkedIn
Share
Instagram