https://a.storyblok.com/f/270183/86587/3fceef375e/removing-pythons-gil.jpg

A remoção do GIL do Python: está acontecendo!

Publicado em October 30, 2023

Tempo de leitura: 9 minutos

Introdução

O Global Interpreter Lock (GIL) no Python é um mecanismo de bloqueio que garante que apenas uma thread possa executar código Python por vez, mesmo em processadores com vários núcleos. Embora isso traga algumas vantagens, isso significa que não é fácil aproveitar os vários núcleos da CPU para processamento paralelo no Python.

Recentemente, o Conselho Diretor do Python indicou que pretende aprovar a PEP 703, uma proposta para criar uma versão do CPython — o interpretador mais popular do Python — sem o GIL. Isso tem implicações de longo alcance para o desenvolvimento do Python no futuro, por isso vale a pena entender o que está acontecendo.

Nesta postagem, discutiremos por que existe o GIL no Python, por que poderíamos querer removê-lo e o trabalho que está sendo feito atualmente para alcançarmos esse objetivo.

Por que temos o GIL?

O GIL simplifica o gerenciamento de threads e protege contra condições de corrida e corrupção de memória no Python, facilitando aos desenvolvedores a escrita de código concorrente com segurança. Ele foi introduzido quando o suporte a threads foi adicionado ao Python, nos primórdios da linguagem.

Compatibilidade

Muitos pacotes do Python (e o principal interpretador do Python, o CPython) fazem uso intenso de extensões em C, que não são inerentemente seguras para threads. É possível que várias threads tentem acessar os mesmos recursos, o que pode levar a efeitos extremamente negativos. O GIL tornou mais seguro criar e usar extensões em C, o que, por sua vez, facilitou para que os desenvolvedores da década de 90 começassem a usar o Python para criar software, impulsionando sua adoção.

Coleta de lixo e contagem de referências

A outra razão importante está relacionada à forma como o Python lida com a coleta de lixo. A coleta de lixo é um processo automático de gerenciamento de memória no qual o interpretador rastreia e libera a memória ocupada por objetos que não são mais referenciados ou acessíveis no programa. No Python, existem dois métodos principais de coleta de lixo, mas o mais destacado é um processo chamado contagem de referências.

A contagem de referências em Python é uma maneira eficiente de gerenciar a memória e garantir que os recursos sejam liberados quando não estiverem mais em uso, ajudando a evitar vazamentos de memória em programas escritos em Python. A contagem de referências funciona assim:

  1. Cada objeto mantém um registro do número de referências que apontam para ele.

  2. Quando a contagem de referências de um objeto chega a zero, isso significa que não há mais referências a esse objeto no programa.

  3. Isso indica que o objeto não é mais necessário.

  4. O sistema de gerenciamento de memória do Python libera automaticamente a memória ocupada pelo objeto, excluindo-o efetivamente.

Sem o GIL, várias threads em execução simultânea poderiam manipular as contagens de referência dos objetos ao mesmo tempo, levando a condições de corrida e corrupção de memória. O GIL atua como uma proteção, permitindo que apenas uma thread execute o bytecode do Python por vez, evitando esses possíveis problemas.

Por que queremos remover o GIL?

Embora o GIL possa facilitar a escrita de código sem que se precise se preocupar muito com a segurança entre threads, ele também pode limitar os ganhos de desempenho que você poderia esperar ao usar múltiplas threads em tarefas limitadas pela CPU. Para tarefas limitadas pela E/S, como fazer solicitações HTTP ou operações com arquivos, o GIL não tem um impacto tão grande, então você ainda pode se beneficiar do uso de múltiplas threads nesses casos. Portanto, se você estiver usando threads em Python para enviar muitas solicitações HTTP, isso pode melhorar o desempenho, mas se estiver usando threads para realizar muitas tarefas que exigem muito da CPU, provavelmente não haverá melhora.

Se o GIL fosse removido, poderíamos obter os ganhos de desempenho decorrentes do uso de threads ao executar tarefas que exigem muito da CPU — ganhos esses que, atualmente, não obtemos no CPython.

O que há de errado com outras abordagens para melhorar a concorrência?

Multithreading e processamento assíncrono com asyncio são métodos para melhorar o desempenho, mas funcionam apenas para operações limitadas pela E/S. No entanto, existem alguns métodos que funcionam para operações limitadas pela CPU.

O Python oferece suporte ao multiprocessamento por meio do multiprocessing módulo, e também é possível escrever código semelhante ao Python usando Cython para melhorar o desempenho. Ambos podem melhorar o desempenho de operações que dependem da CPU, mas apresentam desvantagens.

Multiprocessamento

O multiprocessing permite que você utilize vários núcleos da CPU criando processos separados, cada um com seu próprio interpretador de Python e espaço de memória. Isso é eficaz para tarefas que dependem da CPU, pois contorna o GIL, que opera em threads, e permite que você utilize os vários núcleos da sua máquina para uma execução verdadeiramente paralela.

Infelizmente, a criação de vários processos acarreta um consumo maior de recursos em comparação com as threads, resultando em maior uso de memória e tempo de inicialização. Além disso, a comunicação entre processos é mais lenta e complicada do que entre threads. Por fim, os processos não compartilham memória por padrão, o que torna mais difícil compartilhar dados entre eles e sincronizá-los.

Cython

O Cython é um superconjunto do Python que permite escrever código semelhante ao Python com algumas anotações adicionais para melhorar o desempenho. É uma maneira de fazer com que seu código Python seja executado mais rapidamente sem precisar abandonar completamente a linguagem Python. O Cython pode acelerar a execução do seu código ao otimizar partes críticas, mantendo ao mesmo tempo a facilidade de uso e a legibilidade da linguagem Python.

No entanto, o Cython apresenta uma curva de aprendizado mais acentuada do que o Python e exige conhecimento das construções da linguagem C, o que o torna menos acessível. Escrever e manter código em Cython também pode ser mais complexo e propenso a erros do que escrever código em Python puro, especialmente quando se está reescrevendo código originalmente escrito em Python.

Portanto, existem alguns métodos para melhorar o desempenho, mas ambos apresentam desvantagens consideráveis. Isso explica por que tem havido tanto interesse em remover o GIL do Python.

Tentativas anteriores e por que não deram certo

Vários projetos já tentaram remover o GIL do CPython no passado, com certo sucesso; por exemplo, o projeto Gilectomy . No entanto, os esforços para remover o GIL geralmente trazem algumas desvantagens:

  • É uma tarefa complexa, com o risco de introduzir erros e instabilidade no interpretador do CPython.

  • Elas podem causar falhas no código Python existente e nas bibliotecas que dependem do GIL.

  • Elas podem aumentar o uso de memória.

  • As extensões existentes do Python escritas em C precisariam passar por alterações significativas.

A principal desvantagem, porém, é que os projetos mais antigos sem GIL, na verdade, tornavam o código de thread único mais lento do que com o GIL, o que significa que, se você não estivesse usando multithreading para acelerar operações limitadas pela CPU, seu código teria um desempenho pior do que com o CPython comum, o que não é aceitável.

PEP 703, que o Conselho Diretor do Python pretende aprovar, mudará tudo isso e já conquistou amplo apoio. Então, vamos falar sobre isso!

O que é a PEP 703? Python sem GIL, da maneira certa

A PEP 703 propõe uma maneira de remover o GIL do Python, mas consegue evitar o impacto no desempenho do código não multithread que afetou outros projetos Python sem GIL. O Conselho Diretor do Python indicou, em 28 de julho, que pretende aprovar a PEP, abrindo caminho para que o Python sem GIL entre no mainstream e, eventualmente, se torne o padrão no Python.

Como isso funciona?

A principal técnica importante para permitir que o GIL seja removido sem afetar o desempenho do código de thread único é a contagem de referências ponderada.

Na contagem de referências direcionada, os objetos acessados por uma única thread têm suas contagens de referência gerenciadas de forma mais eficiente do que aqueles acessados por múltiplas threads, proporcionando um aumento de desempenho para programas de thread único, nos quais a maioria dos objetos é utilizada por apenas uma thread. Isso significa que o desempenho do Python sem GIL que utiliza essa técnica em operações de thread único é comparável ao do Python comum, enquanto programas Python multithread limitados pela CPU são muito mais rápidos!

O projeto também torna o Python sem GIL mais eficiente ao adiar o processo de contagem de referências para objetos de módulos de nível superior que provavelmente não sofrerão alterações em um programa Python, além de designar alguns objetos como “imortais” — objetos como None nunca são destruídos e, portanto, não precisam ser contados. Há também mudanças na forma como a memória é alocada para objetos Python, o que facilita a alocação de memória de maneira segura para threads.

Parece ótimo! Então, a questão que realmente se coloca é: quando teremos acesso ao Python sem GIL?

Como o Python sem GIL será implementado

O Conselho Diretor do Python descreveu as três etapas que, em sua visão, o Python sem GIL deverá seguir para se tornar a versão padrão do CPython:

  1. Curto prazo: Na versão 3.13 do Python (ou possivelmente na 3.14), está prevista a introdução da compilação sem GIL como um modo experimental. Esse modo será utilizado para obter insights sobre seu uso, o design da API, o empacotamento e a distribuição.

  2. Médio prazo: Assim que houver confiança de que há apoio suficiente da comunidade para a compilação sem GIL, ela se tornará uma opção suportada, mas não a padrão. Será definida uma data ou versão-alvo para torná-la a opção padrão, com o cronograma dependendo de fatores como compatibilidade de API e prontidão da comunidade. Estima-se que essa fase possa levar um ou dois anos, mas pode demorar mais do que isso.

  3. A longo prazo: O objetivo final é que a compilação sem GIL se torne o padrão, eliminando o GIL sem comprometer a compatibilidade com versões anteriores. Estima-se que possa levar até 5 anos a partir de agora para chegar a esse estágio. Ao longo desse processo, serão realizadas avaliações regulares para garantir que o progresso esteja ocorrendo, evitando ao mesmo tempo problemas de compatibilidade com versões anteriores — ninguém quer ver outra mudança como a provação de 10 anos que foi a introdução do Python 3…

Conclusão

O Python está à beira de uma mudança significativa: a (possível) remoção do Global Interpreter Lock (GIL), o que poderia melhorar consideravelmente a capacidade do Python de utilizar vários núcleos de processador para obter melhor desempenho. O GIL, que costumava funcionar como uma rede de segurança, tem limitado o potencial do Python de utilizar plenamente os processadores modernos em tarefas que dependem da CPU.

Ao contrário das tentativas anteriores, a PEP 703 oferece uma solução inteligente, incorporando contagem de referências tendenciosa, contagem de referências diferida e objetos imortais, entre outras técnicas. Ela garante que programas Python de thread único não fiquem lentos, ao mesmo tempo em que proporciona um aumento de desempenho para aqueles que utilizam múltiplos threads em tarefas que exigem muito da CPU.

Essa mudança ocorrerá em três etapas: primeiro, com um modo de compilação experimental; depois, como uma opção suportada; e, por fim, como configuração padrão, possivelmente nos próximos cinco anos! O objetivo é tornar essa transição suave e evitar problemas de compatibilidade, trazendo mais potência e eficiência ao Python.

Se você tiver alguma dúvida sobre este artigo, sinta-se à vontade para entrar em contato conosco na nossa Slack da Comunidade Vonage e nos perguntar por lá ou nos enviar uma mensagem no X, anteriormente conhecido como Twitter.

Se você estiver interessado em usar APIs para qualquer finalidade relacionada a comunicações, como envio de SMS, chamadas de voz, videoconferências, autenticação de dois fatores, prevenção de fraudes e muito mais, você pode se cadastrar para obter um Account gratuito de desenvolvedor da Vonage (com créditos grátis!).

Compartilhar:

https://a.storyblok.com/f/270183/400x400/92109caf6a/max-kahan.png
Max KahanEx-funcionários da Vonage

Max é um ex-membro da equipe da Vonage. Ele atuou como Promotor de Desenvolvedores Python e Engenheiro de Software, com interesse em APIs de comunicação, aprendizado de máquina, experiência do desenvolvedor e dança! Ele é formado em Física, mas atualmente trabalha em projetos de código aberto e cria soluções para facilitar a vida dos desenvolvedores.