Curso
Se você está começando em Python e quer aprender mais, faça o curso Introduction to Data Science in Python da DataCamp.
O global interpreter lock (GIL) é um mecanismo que aplica um bloqueio global no interpretador. Ele é usado em interpretadores de linguagens para sincronizar e gerenciar a execução de threads, garantindo que apenas uma thread nativa (agendada pelo sistema operacional) seja executada por vez.
Num cenário com múltiplas threads, pode acontecer de duas threads tentarem acessar a mesma área de memória ao mesmo tempo e, com isso, sobrescreverem dados. Por isso, é necessário um mecanismo que evite esse tipo de condição de corrida.
Alguns interpretadores populares que usam GIL são o CPython e o Ruby MRI. Como você provavelmente sabe, Python é uma linguagem interpretada e tem várias distribuições, como CPython, Jython e IronPython. Dentre elas, o GIL é suportado apenas no CPython, que também é a implementação mais usada do Python. O CPython foi desenvolvido em C e Python, principalmente para dar suporte a aplicações com bastante código C por baixo dos panos.
Mesmo que seu processador tenha múltiplos núcleos, o GIL permite que apenas uma thread seja executada por vez. Isso acontece porque, quando uma thread começa a rodar, ela adquire o GIL. Ao aguardar uma operação de I/O (leitura/gravação em disco) ou uma operação intensiva de CPU (como multiplicação de vetores/matrizes), ela libera o lock para que outras threads do processo possam rodar. Assim, você não consegue executar outras threads exatamente ao mesmo tempo.

Vamos analisar o diagrama acima. Existe uma função factorial e duas threads, 1 e 2. A thread 1 está bloqueada (com o lock) enquanto a thread 2 está aguardando. Isso significa que só uma das threads consegue acessar a função por vez. Agora, suponha que a função factorial leve 2 segundos para concluir. Em um caso ideal, as duas threads terminariam em 2 segundos. Mas, no Python, isso não acontece: as threads rodam em série, não em paralelo.
As threads 1 e 2 chamando a função factorial podem demorar o dobro do tempo que uma única thread chamando a função duas vezes. Isso também indica que o gerenciador de memória controlado pelo interpretador não é thread-safe, ou seja, múltiplas threads não conseguem acessar simultaneamente os mesmos dados compartilhados.
Assim, o GIL (Fonte: Understanding the Python GIL):
-
Limita a operação com threads.
-
Restringe a execução em paralelo.
-
Garante que apenas uma thread rode no interpretador por vez.
-
Simplifica vários detalhes de baixo nível, como o gerenciamento de memória.
Com o GIL, você tem computação cooperativa ou multitarefa coordenada, em vez de computação paralela.
Como se observa no diagrama acima, temos três threads: inicialmente a thread 1 está executando e adquiriu o GIL. Quando ocorre uma operação de I/O, como leitura ou escrita, a thread 1 libera o GIL e ele é adquirido pela thread 2. Esse ciclo se repete e o GIL vai alternando entre as threads até que todas concluam a execução do programa. Lembre que qualquer thread sem o lock e que ainda não concluiu sua execução permanece em estado de espera.
GIL no Python 2 vs. Python 3

O Global Interpreter Lock no Python 2.7 funciona de forma diferente do Python 3. Em uma operação puramente CPU-bound, a thread continua rodando, já que não há I/O; nesse caso, as outras threads ficam ociosas ou aguardando, o que não é desejável. Para mitigar isso, o Python 2 usa o conceito de ticks. O GIL faz checagens periódicas para monitorar o estado das threads (se estão aguardando, em I/O ou executando). Porém, o GIL não monitora a cada instante; ele usa ticks e verifica as threads a cada 100 ticks.
Um tick é uma instrução de bytecode e, quando 100 instruções de bytecode são executadas, o GIL checa se as threads estão rodando ou esperando. Importante: tick não está relacionado a tempo, e sim a bytecode. Cada tick pode levar mais ou menos tempo para executar.
Essa checagem periódica a cada 100 ticks é essencial, especialmente em operações CPU-bound, já que não há I/O. Observe que você pode ajustar o contador de ticks usando o módulo interno sys do Python.
Algumas desvantagens dessa abordagem:
Se você observar este diagrama, verá que as threads não rodam em paralelo, mas em série, o que chamamos de computação cooperativa, governada pelo GIL. Além disso, as threads podem esperar por longos períodos. Por exemplo, a thread T3 precisou aguardar T1 e T2 liberarem o GIL. Assim, as threads acabam “passando fome” por lock e por tempo de CPU.
Outra desvantagem é a disputa entre threads para adquirir o GIL. Inicialmente, pela prioridade, T1 pode ter conseguido o GIL, depois T2, e assim por diante. Porém, quando T3 libera o GIL, ela notifica as demais threads, e todas passam a competir novamente pelo lock. Dá para atenuar isso definindo prioridades para cada thread.
No Python 3, há um tempo fixo alocado para cada thread (cerca de 5 ms de execução). Isso evita a “fome” por recursos, já que o tempo de espera é igual para todas as threads. Elas aguardam para ver se a Thread 1 libera o GIL por conta de I/O ou de uma operação de sleep; se não, a liberação é forçada após 5 ms. Assim, não há starvation de CPU, pois cada thread recebe uma fatia de tempo semelhante, o que reduz o problema de disputa pelo GIL.
Vale notar que a nova implementação do GIL dá mais prioridade a tarefas CPU-bound do que a operações de I/O. Se uma thread liberou o GIL e duas estão esperando, o lock tende a ser adquirido pela thread que precisa executar um trabalho CPU-bound.
Conclusão
Parabéns por concluir o tutorial.
Este conteúdo apresentou um tema avançado, com o objetivo de oferecer uma visão teórica de como o GIL funciona no Python. Como não exploramos código sobre GIL e threads, um bom exercício é criar múltiplas threads e um método que execute uma tarefa intensiva de I/O e, em seguida, analisar como o GIL alterna entre as threads.
Fique à vontade para deixar suas dúvidas nos comentários abaixo.
Referências:
Se você está começando em Python e quer aprender mais, faça o curso Introduction to Data Science in Python da DataCamp.