Pergunta

O que é uma trava global de intérprete e por que é um problema?

Muito barulho foi feito para remover o GIL do Python, e eu gostaria de entender por que isso é tão importante. Eu nunca escrevi um compilador nem um intérprete, então não seja frugal com detalhes, provavelmente precisarei deles para entender.

Foi útil?

Solução

O GIL do Python pretende serializar o acesso a intermediários internos de diferentes threads. Em sistemas com vários núcleos, isso significa que vários threads não podem efetivamente usar vários núcleos. (Se o GIL não levasse a esse problema, a maioria das pessoas não se importaria com o GIL - ele só está sendo levantado como um problema devido à crescente prevalência de sistemas multi -core.) Se você quiser entendê -lo em detalhes, você pode ver esse vídeo ou olhe para Este conjunto de slides. Pode ser muita informação, mas você pediu detalhes :-)

Observe que o GIL do Python é realmente um problema para o CPYTHON, a implementação de referência. Jython e Ironpython não têm um gil. Como desenvolvedor do Python, você geralmente não encontra o GIL, a menos que esteja escrevendo uma extensão C. Os escritores de extensão C precisam liberar o GIL quando suas extensões bloqueiam a E/S, para que outros threads no processo Python tenham a chance de executar.

Outras dicas

Suponha que você tenha vários tópicos que não verdade Toque os dados um do outro. Aqueles devem ser executados o mais independentemente possível. Se você tem um "bloqueio global" que precisa adquirir para (digamos) chamar uma função, que pode acabar como um gargalo. Você pode acabar sem se beneficiar de ter vários tópicos em primeiro lugar.

Para colocá -lo em uma analogia do mundo real: Imagine 100 desenvolvedores trabalhando em uma empresa com apenas uma única caneca de café. A maioria dos desenvolvedores passava o tempo esperando café em vez de codificar.

Nada disso é específico do Python - não sei os detalhes do que Python precisava de um GIL em primeiro lugar. No entanto, espero que tenha dado a você uma idéia melhor do conceito geral.

Vamos primeiro entender o que o Python Gil fornece:

Qualquer operação/instrução é executada no intérprete. Gil garante que o intérprete seja mantido por um único tópico em um determinado instante de tempo. E seu programa Python com vários threads funciona em um único intérprete. Em qualquer instante em particular, esse intérprete é mantido por um único thread. Isso significa que apenas o fio que está segurando o intérprete é corrida no qualquer instante de tempo.

Agora, por que isso é um problema:

Sua máquina pode estar tendo vários núcleos/processadores. E vários núcleos permitem que vários threads sejam executados simultaneamente ou seja, vários tópicos podem executar em qualquer instante em particular.. Mas como o intérprete é mantido por um único thread, outros threads não estão fazendo nada, mesmo que tenham acesso a um núcleo. Portanto, você não está obtendo vantagem fornecida por vários núcleos, porque a qualquer instante apenas um núcleo, que é o núcleo usado pelo thread que atualmente mantém o intérprete, está sendo usado. Portanto, seu programa levará tanto tempo para executar como se fosse um único programa encadeado.

No entanto, operações potencialmente bloqueadoras ou de longa execução, como E/S, processamento de imagens e trituração de números numpy, acontecem fora do GIL. Tirado de aqui. Portanto, para essas operações, uma operação multithread ainda será mais rápida que uma única operação rosqueada, apesar da presença de GIL. Então, Gil nem sempre é um gargalo.

EDIT: GIL é um detalhe de implementação do CPython. Ironpython e Jython não têm Gil, então um programa verdadeiramente multithread deve ser possível neles, pensei que nunca usei Pypy e Jython e não tenho certeza disso.

O Python não permite multithreading no sentido mais verdadeiro da palavra. Possui um pacote com vários threading, mas se você deseja multi-thread para acelerar seu código, geralmente não é uma boa ideia usá-lo. O Python possui um construto chamado Lock Global Interpreter (GIL).

https://www.youtube.com/watch?v=ph374fjqfpe

O GIL garante que apenas um de seus 'tópicos' possa executar a qualquer momento. Um tópico adquire o GIL, faz um pouco de trabalho e depois passa o GIL para o próximo tópico. Isso acontece muito rapidamente, de modo que, para o olho humano, pode parecer que seus tópicos estão executando em paralelo, mas eles realmente se revezam usando o mesmo núcleo da CPU. Toda essa passagem GIL adiciona sobrecarga à execução. Isso significa que, se você deseja fazer seu código funcionar mais rápido, o uso do pacote de rosqueamento geralmente não é uma boa ideia.

Existem razões para usar o pacote de encadeamento do Python. Se você deseja executar algumas coisas simultaneamente, e a eficiência não é uma preocupação, é totalmente bom e conveniente. Ou se você estiver executando o código que precisa esperar por algo (como algum IO), isso pode fazer muito sentido. Mas a biblioteca de threading não permite usar núcleos extras de CPU.

A multi-threading pode ser terceirizada para o sistema operacional (fazendo multiprocessamento), algum aplicativo externo que chama seu código Python (por exemplo, Spark ou Hadoop) ou algum código que seu código Python chama (por exemplo: você pode ter seu python Código chama uma função C que faz o caro material multithread).

Sempre que dois threads têm acesso à mesma variável, você tem um problema. Em C ++, por exemplo, a maneira de evitar o problema é definir algum bloqueio mutex para impedir que dois encadeamentos, digamos, digite o setter de um objeto ao mesmo tempo.

O multithreading é possível no Python, mas dois threads não podem ser executados ao mesmo tempo em uma granularidade mais fina do que uma instrução Python. O tópico em execução está recebendo um bloqueio global chamado Gil.

Isso significa que, se você começar a escrever algum código multithread para aproveitar seu processador multicore, seu desempenho não melhorará. A solução alternativa usual consiste em ir multiplicado.

Observe que é possível lançar o GIL se você estiver dentro de um método que você escreveu em C, por exemplo.

O uso de um GIL não é inerente ao Python, mas a alguns de seus intérpretes, incluindo o cpython mais comum. (#edited, veja comentário)

A questão do GIL ainda é válida no Python 3000.

Documentação Python 3.7

Eu também gostaria de destacar a seguinte citação do Pitão threading documentação:

Detalhe da implementação do CPYTHON: No CPYTHON, devido ao bloqueio global do intérprete, apenas um thread pode executar o código Python de uma só vez (mesmo que certas bibliotecas orientadas para o desempenho possam superar essa limitação). Se você deseja que seu aplicativo faça melhor uso dos recursos computacionais de máquinas multi-core, é aconselhável usar multiprocessing ou concurrent.futures.ProcessPoolExecutor. No entanto, o encadeamento ainda é um modelo apropriado se você deseja executar várias tarefas ligadas a E/S simultaneamente.

Isso vincula ao Glossário de entrada para global interpreter lock o que explica que o GIL implica que o paralelismo rosqueado em Python é inadequado para Tarefas ligadas à CPU:

O mecanismo usado pelo intérprete Cpython para garantir que apenas um thread execute o bytecode Python por vez. Isso simplifica a implementação do CPYTHON, tornando o modelo de objeto (incluindo tipos internos críticos, como o dict) implicitamente seguro contra o acesso simultâneo. O bloqueio de todo o intérprete facilita para o intérprete ser multithread, às custas de grande parte do paralelismo oferecido por máquinas multiprocessador.

No entanto, alguns módulos de extensão, padrão ou de terceiros, são projetados para liberar o GIL ao realizar tarefas intensivas em computação, como compactação ou hash. Além disso, o GIL é sempre lançado ao fazer E/S.

Os esforços anteriores para criar um intérprete de "thread livre" (que bloqueia dados compartilhados em uma granularidade muito mais fina) não foi bem-sucedida porque o desempenho sofreu no caso comum de processador único. Acredita -se que a superação dessa questão de desempenho tornaria a implementação muito mais complicada e, portanto, mais cara de manter.

Esta citação também implica que os ditos e, portanto, a atribuição variável também são seguros como um detalhe de implementação do CPYTHON:

Em seguida, o documentos para o multiprocessing pacote Explique como ele supera o processo GIL por desova ao expor uma interface semelhante à de threading:

O multiprocessamento é um pacote que suporta processos de desova usando uma API semelhante ao módulo de rosqueamento. O pacote multiprocessamento oferece simultaneidade local e remota, efetivamente a estação de lado da trava global usando subprocessos em vez de threads. Devido a isso, o módulo multiprocessamento permite que o programador aproveite totalmente vários processadores em uma determinada máquina. Ele é executado no Unix e no Windows.

E a documentos para concurrent.futures.ProcessPoolExecutor Explique que ele usa multiprocessing Como um back -end:

A classe ProcessPoolExecutor é uma subclasse de executor que usa um pool de processos para executar chamadas de forma assíncrona. O ProcessPoolExecutor usa o módulo multiprocessamento, que permite que ele se encaixe no bloqueio global do intérprete, mas também significa que apenas objetos de opção podem ser executados e devolvidos.

que deve ser contrastado com a outra classe base ThreadPoolExecutor este usa threads em vez de processos

ThreadpoolExecutor é uma subclasse executor que usa um pool de threads para executar chamadas de forma assíncrona.

do qual concluímos que ThreadPoolExecutor é adequado apenas para tarefas de E/S, enquanto ProcessPoolExecutor Também pode lidar com tarefas ligadas à CPU.

A pergunta seguinte pergunta por que o GIL existe em primeiro lugar: Por que o bloqueio global do intérprete?

Processo vs Experiências de threads

No Multiprocessamento vs Threading Python Eu fiz uma análise experimental do processo versus threads no Python.

Visualização rápida dos resultados:

enter image description here

Por que Python (Cpython e outros) usa o Gil

A partir de http://wiki.python.org/moin/globalinterpreterlock

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

Como removê -lo do Python?

Como Lua, talvez Python possa iniciar várias VM, mas Python não faz isso, acho que deve haver outras razões.

Em Numpy ou alguma outra biblioteca estendida de Python, às vezes, liberando o GIL para outros threads pode aumentar a eficiência de todo o programa.

Quero compartilhar um exemplo do livro Multithreading para efeitos visuais. Então, aqui está uma situação clássica de bloqueio morto

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Agora considere os eventos na sequência resultando em um bloqueio morto.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
║   ║ Main Thread                            ║ Other Thread                         ║
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
║ 1 ║ Python Command acquires GIL            ║ Work started                         ║
║ 2 ║ Computation requested                  ║ MyCallback runs and acquires MyMutex ║
║ 3 ║                                        ║ MyCallback now waits for GIL         ║
║ 4 ║ MyCallback runs and waits for MyMutex  ║ waiting for GIL                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top