Por que o Python foi escrito com o GIL?

112

O bloqueio global de intérpretes (GIL) parece ser frequentemente citado como um dos principais motivos pelos quais a segmentação e afins é um pouco complicada no Python - o que levanta a questão "Por que isso foi feito em primeiro lugar?"

Não sendo um programador, não tenho idéia do porquê disso - qual era a lógica por trás da inserção do GIL?

Fomite
fonte
10
O artigo da Wikipedia afirma que "o GIL pode ser uma barreira significativa ao paralelismo - um preço pago por ter o dinamismo da linguagem" e continua dizendo que "os motivos para empregar esse bloqueio incluem: aumento da velocidade de programas de thread único (não é necessário adquirir ou liberar bloqueios em todas as estruturas de dados separadamente) e fácil integração de bibliotecas C que geralmente não são seguras para threads ".
Robert Harvey
3
@RobertHarvey, o dinamismo não tem nada a ver com isso. O problema é mutação.
dan_waterworth
11
Não posso deixar de sentir que, como a falta de números numéricos não assinados do Java, ele tinha o objetivo de impedir que pessoas que não sabem o que estão fazendo atirem no próprio pé. Infelizmente, qualquer um que não sabem o que estão fazendo recebe uma linguagem deficiente, que é uma verdadeira vergonha, porque rochas Python em tantas outras maneiras
Básicos
11
@Basic: deve haver alguma maneira padrão de lidar com matrizes de bytes em Java (eu não a uso há muito tempo) para fazer cálculos criptográficos. O Python (por exemplo) não possui números assinados, mas eu nem tentaria fazer operações bit a bit com ele, porque existem maneiras melhores.
Nick T

Respostas:

105

Existem várias implementações de Python, por exemplo, CPython, IronPython, RPython, etc.

Alguns deles têm um GIL, outros não. Por exemplo, o CPython possui o GIL:

De http://en.wikipedia.org/wiki/Global_Interpreter_Lock

Os aplicativos escritos em linguagens de programação com um GIL podem ser projetados para usar processos separados para obter paralelismo total, pois cada processo tem seu próprio intérprete e, por sua vez, seu próprio GIL.

Benefícios do GIL

  • Maior velocidade de programas de thread único.
  • Fácil integração de bibliotecas C que geralmente não são seguras para threads.

Por que Python (CPython e outros) usa o GIL

No CPython, o bloqueio global de intérpretes, ou GIL, é um mutex que impede que vários threads nativos executem bytecodes do Python de uma só vez. Esse bloqueio é necessário principalmente porque o gerenciamento de memória do CPython não é seguro para threads.

O GIL é controverso porque impede que os programas CPython multithread aproveitem ao máximo os sistemas multiprocessadores em determinadas situações. Observe que operações potencialmente bloqueadoras ou de execução demorada, como E / S, processamento de imagens e processamento de números NumPy, ocorrem fora do GIL. Portanto, é apenas em programas multithread que passam muito tempo dentro do GIL, interpretando o bytecode do CPython, que o GIL se torna um gargalo.

O Python possui um GIL em oposição ao bloqueio de baixa granularidade por vários motivos:

  • É mais rápido no caso de rosca única.

  • É mais rápido no caso multithread para programas vinculados de E / S.

  • É mais rápido no caso multithread para programas vinculados à CPU que fazem seu trabalho intensivo em computação nas bibliotecas C.

  • Isso facilita a escrita das extensões C: não haverá troca de threads do Python, exceto onde você permitir que isso aconteça (ou seja, entre as macros Py_BEGIN_ALLOW_THREADS e Py_END_ALLOW_THREADS).

  • Isso facilita a quebra de bibliotecas C. Você não precisa se preocupar com a segurança da linha. Se a biblioteca não for segura para threads, basta manter o GIL bloqueado enquanto você o chama.

O GIL pode ser liberado por extensões C. A biblioteca padrão do Python libera o GIL em torno de cada chamada de E / S de bloqueio. Portanto, o GIL não tem conseqüências para o desempenho de servidores vinculados de E / S. Assim, você pode criar servidores de rede em Python usando processos (bifurcação), threads ou E / S assíncrona, e o GIL não interferirá em seu caminho.

Bibliotecas numéricas em C ou Fortran também podem ser chamadas com o GIL liberado. Enquanto sua extensão C estiver aguardando a conclusão de uma FFT, o intérprete estará executando outros threads do Python. Um GIL é, portanto, mais fácil e rápido do que o bloqueio refinado neste caso também. Isso constitui a maior parte do trabalho numérico. A extensão NumPy libera o GIL sempre que possível.

Threads geralmente são uma maneira ruim de escrever a maioria dos programas de servidor. Se a carga for baixa, o garfo será mais fácil. Se a carga for alta, a E / S assíncrona e a programação orientada a eventos (por exemplo, usando a estrutura Twisted do Python) são melhores. A única desculpa para o uso de threads é a falta de os.fork no Windows.

O GIL é um problema se, e somente se, você estiver fazendo um trabalho intensivo de CPU em Python puro. Aqui você pode obter um design mais limpo usando processos e passagem de mensagens (por exemplo, mpi4py). Há também um módulo 'processing' na loja de queijos Python, que fornece aos processos a mesma interface que os threads (por exemplo, substitua threading.Thread por processing.Process).

Os encadeamentos podem ser usados ​​para manter a capacidade de resposta de uma GUI, independentemente do GIL. Se o GIL prejudicar seu desempenho (consulte a discussão acima), você pode permitir que seu thread inicie um processo e aguarde o término.

Md Mahbubur Rahman
fonte
52
Soa como uvas azedas para mim. O Python não pode executar threads adequadamente, então você cria razões pelas quais os threads são desnecessários ou até ruins. "Se a carga estiver baixa, forquear é mais fácil", sério? E o GIL é "mais rápido" para todos esses casos apenas se você insistir em usar o GC com contagem de referências.
Michael Borgwardt
9
s/RPython/PyPy/g. @MichaelBorgwardt Dar razões pro GIL é o tipo de questão, não é? Embora eu concorde que parte do conteúdo desta resposta (ou seja, discussão de alternativas) não vem ao caso. E para o bem ou para o mal, agora é quase impossível se livrar da recontagem - está profundamente arraigado em toda a API e na base de código; é quase impossível se livrar dele sem reescrever metade do código e quebrar todo o código externo.
10
Não esqueça a multiprocessingbiblioteca - padrão desde 2.6. Seus pools de trabalhadores são uma abstração super-lisa para alguns tipos simples de paralelismo.
Sean McSomething
8
@alcalde Somente se você não sabe o que está fazendo e / ou não deseja que seus threads funcionem cooperativamente / se comuniquem. Caso contrário, é uma dor real nas costas, especialmente considerando a sobrecarga de lançar um novo processo em alguns sistemas operacionais. Como temos servidores com 32 núcleos, para utilizá-los totalmente no CPython, preciso de 32 processos. Essa não é uma "boa solução", é um truque para solucionar as inadequações do CPython.
Basic
8
O fato de existirem encadeamentos em plataformas diferentes do Windows deve ser prova suficiente de que a bifurcação não é adequada em todas as situações.
Zneak 14/10/2015
42

Primeiro: Python não tem um GIL. Python é uma linguagem de programação. Uma linguagem de programação é um conjunto de regras e restrições matemáticas abstratas. Não há nada na especificação da linguagem Python que diga que deve haver um GIL.

Existem muitas implementações diferentes do Python. Alguns têm um GIL, outros não.

Uma explicação simples para ter um GIL é que escrever código simultâneo é difícil. Ao colocar um bloqueio gigante em torno do seu código, você o força a executar sempre em série. Problema resolvido!

No CPython, em particular, um objetivo importante é facilitar a extensão do intérprete com plug-ins escritos em C. Novamente, escrever código simultâneo é difícil, portanto, garantindo que não haverá simultaneidade, é mais fácil escrever extensões para o intérprete. Além disso, muitas dessas extensões são apenas invólucros finos em torno de bibliotecas existentes que podem não ter sido escritas com a simultaneidade em mente.

Jörg W Mittag
fonte
6
Esse é o mesmo argumento da falta de tipos numéricos não assinados do Java - os desenvolvedores acham que todos os outros são mais burros do que são ... #
Basic
11
@Basic - acredite ou não, mesmo quando você não é realmente muito burro, acontece que ter uma linguagem que faz suposições simplificadoras que significa que você não pensa em certas coisas para fazê-las funcionar ainda é útil coisa. O CPython é ótimo para certas coisas, incluindo aplicativos multithread simples (onde o programa é vinculado por E / S, muitos são e, portanto, o GIL não importa), porque as decisões de design que tornaram o GIL a melhor solução também facilitam a programação desses aplicativos. , particularmente o fato de suportar operações atômicas em coleções .
Jules
@Jules Sim, é muito útil até você precisar desses recursos. A solução "preferida" do cpython de "apenas escrevê-lo em outra linguagem como c ++" significa que você perde todos os benefícios individuais do python. Se você está escrevendo metade do seu código em c ++, por que começar do Python? Claro, para pequenos projetos de API / cola é rápido e fácil, e para ETL é inigualável, mas não é adequado para qualquer coisa que exija trabalho pesado. É o mesmo que usar Java para falar com hardware ... É quase cômico o que você precisa fazer.
Básico
16

Qual é o objetivo de um GIL?

A documentação do CAPI tem a dizer sobre o assunto:

O interpretador Python não é totalmente seguro para threads. Para oferecer suporte a programas Python multithread, existe um bloqueio global, chamado de interpretador global ou GIL, que deve ser mantido pelo thread atual antes que ele possa acessar com segurança objetos Python. Sem o bloqueio, mesmo as operações mais simples podem causar problemas em um programa multithread: por exemplo, quando dois 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.

Em outras palavras, o GIL evita a corrupção do estado. Os programas Python nunca devem produzir uma falha de segmentação, porque apenas operações seguras à memória são permitidas. O GIL estende essa garantia a programas multithread.

Quais são as alternativas?

Se o objetivo do GIL é proteger o estado da corrupção, uma alternativa óbvia é bloquear um grão muito mais fino; talvez em um nível por objeto. O problema é que, embora tenha sido demonstrado que aumenta o desempenho de programas multithread, ele tem mais sobrecarga e os programas thread único sofrem como resultado.

dan_waterworth
fonte
2
Seria ótimo permitir que um usuário executasse um programa com uma opção de intérprete, substituindo o gil por um bloqueio refinado, e de alguma forma saber - de uma maneira somente leitura - se o processo atual foi gerado com ou sem o gil.
Luis Masuelli
Apesar do GIL, consegui produzir uma falha de segmentação em um programa multithread devido ao uso descuidado do módulo pyodbc. Portanto, "nunca deve produzir uma falha de segmentação" é uma falácia.
Muposat