Eu concordo, não considero um problema agora que no 2.6 o módulo de multiprocessamento foi adicionado para permitir que você programe usando vários processos de maneira semelhante a um thread. docs.python.org/library/multiprocessing.html
Em geral, para qualquer problema de segurança de thread, você precisará proteger suas estruturas de dados internas com bloqueios. Isso pode ser feito com vários níveis de granularidade.
Você pode usar um bloqueio de granulação fina, onde cada estrutura separada tem seu próprio bloqueio.
Você pode usar o bloqueio de granulação grossa, onde um bloqueio protege tudo (a abordagem GIL).
Existem vários prós e contras de cada método. O bloqueio refinado permite maior paralelismo - duas threads podem ser executadas em paralelo quando não compartilham nenhum recurso. No entanto, há uma sobrecarga administrativa muito maior. Para cada linha de código, você pode precisar adquirir e liberar vários bloqueios.
A abordagem de granulação grossa é o oposto. Dois encadeamentos não podem ser executados ao mesmo tempo, mas um encadeamento individual será executado mais rápido porque não está realizando muitos registros. Em última análise, tudo se resume a uma troca entre velocidade de thread único e paralelismo.
Houve algumas tentativas de remover o GIL no python, mas a sobrecarga extra para máquinas de thread único geralmente era muito grande. Alguns casos podem realmente ser mais lentos, mesmo em máquinas com vários processadores, devido à contenção de bloqueio.
As outras linguagens compiladas para bytecode empregam um mecanismo semelhante?
Isso varia e provavelmente não deve ser considerado uma propriedade de linguagem tanto quanto uma propriedade de implementação. Por exemplo, existem implementações Python, como Jython e IronPython, que usam a abordagem de threading de sua VM subjacente, em vez de uma abordagem GIL. Além disso, a próxima versão de Ruby parece estar se movendo em direção à introdução de um GIL.
você pode explicar isso: 'Dois threads não podem ser executados ao mesmo tempo'? Recentemente, escrevi um servidor web simples em Python com multithreading. Para cada nova solicitação do cliente, os servidores geram um novo thread para ele e esse thread continua em execução. Portanto, haverá vários threads em execução ao mesmo tempo, certo? Ou eu entendi de forma errada?
avi
1
@avi AFAIK python threads não podem ser executados simultaneamente, mas isso não significa que um thread deve bloquear o outro. GIL significa apenas que apenas um thread pode interpretar o código Python por vez, não significa que o gerenciamento de threads e a alocação de recursos não funcionem.
Benproductions 1 de
2
^ então, em qualquer ponto do tempo, apenas um thread estará servindo conteúdo para o cliente ... então não adianta usar multithreading para melhorar o desempenho. certo?
avi de
E, é claro, o Java é compilado para código de bytes e permite um bloqueio muito refinado.
Warren Dew
3
@avi, um processo vinculado a IO como um servidor da web ainda pode ganhar com threads do Python. Dois ou mais threads podem fazer IO simultaneamente. Eles simplesmente não podem ser interpretados (CPU) simultaneamente.
O interpretador Python não é totalmente thread-safe. Para oferecer suporte a programas Python multi-threaded, há um bloqueio global que deve ser mantido pelo thread atual antes que ele possa acessar objetos Python com segurança. Sem o bloqueio, mesmo as operações mais simples podem causar problemas em um programa multi-threaded: por exemplo, quando duas threads incrementam simultaneamente a contagem de referência do mesmo objeto, a contagem de referência pode acabar sendo incrementada apenas uma vez em vez de duas vezes.
Portanto, existe a regra de que apenas o thread que adquiriu o bloqueio do interpretador global pode operar em objetos Python ou chamar funções de API Python / C. Para oferecer suporte a programas Python multi-threaded, o interpretador regularmente libera e readquire o bloqueio - por padrão, a cada 100 instruções de bytecode (isso pode ser alterado com sys.setcheckinterval ()). O bloqueio também é liberado e readquirido em torno de operações de E / S potencialmente bloqueadoras, como ler ou gravar um arquivo, para que outras threads possam ser executadas enquanto a thread que solicita a E / S está esperando a conclusão da operação de E / S.
Eu também li, mas não consigo entender por que Python é diferente nesse aspecto de, digamos, java (é?)
Federico A. Ramponi
@EliBendersky Python threads são implementados como pthreads e são tratados pelo sistema operacional ( dabeaz.com/python/UnderstandingGIL.pdf ) enquanto os threads Java são threads de nível de aplicativo cujo agendamento é tratado pela JVM
gokul_uf
19
O bloqueio de interpretador global é um grande bloqueio do tipo mutex que protege os contadores de referência de serem bloqueados. Se você está escrevendo código Python puro, tudo isso acontece nos bastidores, mas se você embutir Python em C, então você pode ter que pegar / liberar explicitamente o bloqueio.
Este mecanismo não está relacionado ao Python sendo compilado para bytecode. Não é necessário para Java. Na verdade, ele nem mesmo é necessário para Jython (python compilado para jvm).
"Este mecanismo não está relacionado ao Python sendo compilado para bytecode": Precisamente, é um artefato da implementação do CPython. Outras implementações (como Jython que você mencionou) podem estar livres dessa restrição em virtude de sua implementação thread-safe
Eli Bendersky
11
Python, como perl 5, não foi projetado desde o início para ser thread-safe. Threads foram enxertados após o fato, portanto, o bloqueio do intérprete global é usado para manter a exclusão mútua para onde apenas um thread está executando o código em um determinado momento nas entranhas do interpretador.
Threads individuais do Python são multitarefas cooperativamente pelo próprio interpretador, alternando o bloqueio de vez em quando.
É necessário travar você mesmo quando estiver conversando com o Python a partir de C, quando outras threads do Python estiverem ativas para 'aceitar' este protocolo e certificar-se de que nada de inseguro aconteça nas suas costas.
Outros sistemas que possuem uma herança de segmento único que posteriormente evoluiu para sistemas de segmento múltiplo geralmente possuem algum mecanismo desse tipo. Por exemplo, o kernel do Linux tem o "Big Kernel Lock" de seus primeiros dias de SMP. Gradualmente, ao longo do tempo, conforme o desempenho de multithreading se torna um problema, há uma tendência de tentar quebrar esses tipos de bloqueios em pedaços menores ou substituí-los por algoritmos sem bloqueio e estruturas de dados sempre que possível para maximizar o rendimento.
1 por mencionar o fato de que o bloqueio de granulação grossa é usado do que a maioria pensa, especialmente o BKL frequentemente esquecido (eu uso reiserfs- a única razão real que eu sei sobre isso).
novo123456
3
O Linux tinha BKL, desde a versão 2.6.39, BKL foi removido completamente.
avi
5
Claro. Lembre-se de que isso aconteceu cerca de 3 anos depois de eu responder à pergunta. =)
Edward KMETT
7
Com relação à sua segunda pergunta, nem todas as linguagens de script usam isso, mas isso apenas as torna menos poderosas. Por exemplo, os tópicos em Ruby são verdes e não nativos.
No Python, os threads são nativos e o GIL apenas impede que eles sejam executados em núcleos diferentes.
Em Perl, os threads são ainda piores. Eles apenas copiam o interpretador inteiro e estão longe de ser tão utilizáveis quanto no Python.
Respostas:
Em geral, para qualquer problema de segurança de thread, você precisará proteger suas estruturas de dados internas com bloqueios. Isso pode ser feito com vários níveis de granularidade.
Você pode usar um bloqueio de granulação fina, onde cada estrutura separada tem seu próprio bloqueio.
Você pode usar o bloqueio de granulação grossa, onde um bloqueio protege tudo (a abordagem GIL).
Existem vários prós e contras de cada método. O bloqueio refinado permite maior paralelismo - duas threads podem ser executadas em paralelo quando não compartilham nenhum recurso. No entanto, há uma sobrecarga administrativa muito maior. Para cada linha de código, você pode precisar adquirir e liberar vários bloqueios.
A abordagem de granulação grossa é o oposto. Dois encadeamentos não podem ser executados ao mesmo tempo, mas um encadeamento individual será executado mais rápido porque não está realizando muitos registros. Em última análise, tudo se resume a uma troca entre velocidade de thread único e paralelismo.
Houve algumas tentativas de remover o GIL no python, mas a sobrecarga extra para máquinas de thread único geralmente era muito grande. Alguns casos podem realmente ser mais lentos, mesmo em máquinas com vários processadores, devido à contenção de bloqueio.
Isso varia e provavelmente não deve ser considerado uma propriedade de linguagem tanto quanto uma propriedade de implementação. Por exemplo, existem implementações Python, como Jython e IronPython, que usam a abordagem de threading de sua VM subjacente, em vez de uma abordagem GIL. Além disso, a próxima versão de Ruby parece estar se movendo em direção à introdução de um GIL.
fonte
O seguinte é do Manual de referência oficial da API Python / C :
Acho que resume o problema muito bem.
fonte
O bloqueio de interpretador global é um grande bloqueio do tipo mutex que protege os contadores de referência de serem bloqueados. Se você está escrevendo código Python puro, tudo isso acontece nos bastidores, mas se você embutir Python em C, então você pode ter que pegar / liberar explicitamente o bloqueio.
Este mecanismo não está relacionado ao Python sendo compilado para bytecode. Não é necessário para Java. Na verdade, ele nem mesmo é necessário para Jython (python compilado para jvm).
veja também esta questão
fonte
Python, como perl 5, não foi projetado desde o início para ser thread-safe. Threads foram enxertados após o fato, portanto, o bloqueio do intérprete global é usado para manter a exclusão mútua para onde apenas um thread está executando o código em um determinado momento nas entranhas do interpretador.
Threads individuais do Python são multitarefas cooperativamente pelo próprio interpretador, alternando o bloqueio de vez em quando.
É necessário travar você mesmo quando estiver conversando com o Python a partir de C, quando outras threads do Python estiverem ativas para 'aceitar' este protocolo e certificar-se de que nada de inseguro aconteça nas suas costas.
Outros sistemas que possuem uma herança de segmento único que posteriormente evoluiu para sistemas de segmento múltiplo geralmente possuem algum mecanismo desse tipo. Por exemplo, o kernel do Linux tem o "Big Kernel Lock" de seus primeiros dias de SMP. Gradualmente, ao longo do tempo, conforme o desempenho de multithreading se torna um problema, há uma tendência de tentar quebrar esses tipos de bloqueios em pedaços menores ou substituí-los por algoritmos sem bloqueio e estruturas de dados sempre que possível para maximizar o rendimento.
fonte
reiserfs
- a única razão real que eu sei sobre isso).Com relação à sua segunda pergunta, nem todas as linguagens de script usam isso, mas isso apenas as torna menos poderosas. Por exemplo, os tópicos em Ruby são verdes e não nativos.
No Python, os threads são nativos e o GIL apenas impede que eles sejam executados em núcleos diferentes.
Em Perl, os threads são ainda piores. Eles apenas copiam o interpretador inteiro e estão longe de ser tão utilizáveis quanto no Python.
fonte
Talvez este artigo da BDFL ajude.
fonte