O que é Global Interpreter Lock (GIL)? – python característica-linguagem

Pergunta:


Uma das primeiras coisas que se lê ao começar a estudar sobre threads em Python (CPython) é sobre o Global Interpreter Lock (GIL).

  • O que exatamente é o GIL?
  • Quais são suas implicações práticas sobre uma aplicação que utiliza threads?

Lendo superficialmente, inferi que devido às implementações do CPython, apenas uma thread é analisada pelo interpretador por vez.

  • Isso implica que as threads não são executadas em paralelo?
  • Uma thread deve liberar o interpretador para que as outras sejam executadas?
  • Se sim, quando isso ocorre?

Dada que é uma característica do CPython,

  • GIL é exclusivo para o CPython ou outros interpretadores também possuem tal característica?
Autor da pergunta Anderson Carlos Woss

jsbueno

O que exatamente é o GIL?

O Global Interpreter Lock é uma flag que existe no interpretador do Python, e faz com que apenas uma sequência de bytecode na VM de Python seja executada por vez.

A razão inicial de sua criação é que o gerenciamento interno de memória do interpretador Python não é thread-safe. Isso é: o código em C que reserva memória para criação de objetos, mesmo os simples como inteiros ou tuplas, usa o GIL como Mutex para garantir que não vai ser interrompido pelo sistema operacional, numa mudança de thread, e deixando as estruturas de dados internas que o Python usa para memória num estado inconsistente.

Além disso, O GIL é o responsável pelas estruturas de dados do Python como listas, dicionários e, mais importante, as estruturas internas como frames de execução, os dicionários que armazenam variáveis, etc… funcionem de forma transparente em código que use várias threads.

Basicamente, o código central da VM do Python, a estrutura de “dispatch” que é um grande “switch case” para o bytecode só “anda” se tiver controle do GIL.

Acho que a fonte mais relevante em inglês sobre o GIL está no wiki do Python: https://wiki.python.org/moin/GlobalInterpreterLock (muito da informação que está lá está abaixo também)

Quais são suas implicações práticas sobre
uma aplicação que utiliza threads? Lendo superficialmente, inferi que
devido às implementações do CPython, apenas uma thread é analisada
pelo interpretador por vez.
Isso implica que as threads não são executadas em paralelo? Uma thread
deve liberar o interpretador para que as outras sejam executadas? Se
sim, quando isso ocorre?

Sim, enquanto a thread estiver executando código Python. Isso evita que outra thread seja ativada no meio de uma expressão, e altere valores de variáveis em transações que tem que ser atômicas.

No entanto em todos os momentos que uma thread vai fazer uma ação bloqueante – ou seja, algo que não depende de executar mais bytecode, mas vai esperar a leitura de dados de um arquivo, ou de um socket, ou ainda vai simplesmente chamar uma função em código nativo (em geral em C), que não tem perigo de conflitar com o estado interno do interpretador Python, o GIL é “liberado”: ou seja,a thread em execução libera o GIL, e outras threads podem executar seu código (mesmo que um núcleo CPU esteja em 100% processando código nativo).

Por isso, programas que usam threads para atender várias requisições de internet, ou processar arquivos de um disco lento, ou fazer cálculos numéricos pesados usando o NumPy, por exemplo, não sofrem tanto impacto do GIL. No entanto, programas que tenham algoritmos complexos em Python puro, por exemplo, várias operações em strings pequenas, sofrem um impacto grande – aí sim acontece de “só rodar uma thread por vez”.

Em particular quando se usa o NumPy para processamento numérico, ele faz naturalmente uso de múltiplos núcleos da CPU, sem que o desenvolvedor tenha que se preocupar com threads ou qualquer outro recurso na parte em Python do código.

Dada que é uma característica do CPython,
GIL é exclusivo para o CPython ou outros interpretadores também
possuem tal característica?

Em geral outras implementações do Python, como o Pypy vão ter um GIL também. O Jython e o IronPython usam o mecanismo de suas respectivas VMs para poder ter código consistente em threads, e não tem o GIL.

Continuando com as partes não perguntadas:

E como faz então?

Multiprocessing

https://docs.python.org/3/library/multiprocessing.html

A forma mais simples de ter código multithreaded efetivamente rodando em paralelo, sem preocupações com o GIL é o multiprocessing: é uma API criada para ser compatível com o threading, no entanto, cria um novo processo para o que seria uma thread. Os pontos negativos são: criar um novo processo em termos de recurso do sistema é algo muito mais “caro” do que criar uma thread, e, a passagem de argumentos entre os subprocessos é feita serializando-se todos os dados com pickle. Isso causa um overhead, e impede que objetos não serializáveis (como arquivos abertos, sockets, etc…) sejam passados como parâmetros de forma ordinária.

De qualquer forma, veja também o concurrent.futures – é uma biblioteca que permite o uso de pools de threads e processos de forma bem simplificada, com várias estruturas de controle que facilitam a criação de poucos processos ou threads e reaproveitam os mesmos de forma eficiente para executar várias vezes as tarefas. https://docs.python.org/3/library/concurrent.futures.html

asyncio

https://docs.python.org/3/library/asyncio.html

Começando no Python 3.4, o asyncio é a nova onda de programação eficiente em Python. Basicamente foi acrescentado suporte na linguagem, inicialmente apenas com a biblioteca, e no Python 3.5 com sintaxe específica para que a distribuição de várias tarefas que tipicamente eram executadas numa aplicação de servidor em threads distintas fiquem todas na mesma thread, e o uso de co-rotinas permite a troca de contexto entre as tarefas distintas.

AsyncIO não exatamente “dribla” o GIL – é apenas uma forma de programação concorrente que usa explicitamente uma única thread. Tipicamente nos mesmos pontos em que o GIL seria liberado no código, a thread passa a rodar outra tarefa assíncrona. Para os casos em que as tarefas finais não tenham implementações assíncronas, é necessário executa-las explicitamente num worker que pode ser em outra thread, ou outro processo, de forma muito parecida com o que é feito com o concurrent.futures.

Cython

http://cython.org/

Um jeito interessante de escrever seu algoritmo “intenso” em Python puro e não estar submetido ao GIL é usar o Cython. Cython é um super-conjunto da linguagem Python que traduz a sintaxe de Python direto para código C usando a Python API, mas com a opção de ter tipagem estática para variáveis que precisam ser acessadas mais rápidas. Se você tiver certeza que um trecho com bastante uso da CPU não vai interferir em variáveis fora da thread atual, o Cython tem chamadas para liberar o GIL explicitamente.

Se você vai programar suas próprias extensões de Python em código nativo, seja em C ou uma linguagem como Go, Rust, C++, etc… você é responsável, da mesma forma que programando em Cython, por liberar o GIL. Mais informações aqui: https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock

Celery

http://www.celeryproject.org/

Se você tem que criar código de backend para atender várias requisições em paralelo, com uso intenso de CPU, esse é o caminho a seguir:
Celery é um conjunto de ferramentas que permite a distribuição de tarefas usando um “broker” terceirizado. Disparar uma tarefa para ser executada de forma assíncrona com celery é tão simples quanto chamar uma função, mas tem algumas vantagens sobre os outros métodos: Os seus workers de celery não precisam estar nem na mesma máquina – só é necessário que todos os workers possam se comunicar com o processo broker (que pode ser um banco como o redis, o rabbitmq, ou as próprias Queues da Amazon (SQS)). Além disso, o celery tem embutido um sistema simples de re-execução de uma tarefa caso elas não sejam completadas em tempo hábil.
Celery é bem simples de usar em uma única máquina, e é tranquilo configurar num conjunto de máquinas na nuvem que tenha escalabilidade horizontal com preocupação praticamente zero.

PEP 554 – Múltiplos interpretadores

https://www.python.org/dev/peps/pep-0554/

Quando a PEP-554 for implementada (em algum ponto do Python 3.8 pra frente), será possível ter código de Python puro que tenha múltiplas instâncias do interpretador CPython no mesmo processo: isso vai permitir um GIL para cada interpretador. (No entanto, código usando múltiplos interpretadores dessa forma é algo bem avançado e ainda vai ser um pouco experimental – só mencionei aqui por que estamos prestes a superar a ‘barreira de um GIL’ com isso)

Outros

Além das formas citadas, há outros jeitos de se contornar o GIL – em geral outras bibliotecas de execução remota de código: o Celery não é a única, é apenas a mais usada. Há um protótipo do Pypy que tenta usar uma abordagem de “memória transacional” e remover o GIL, e há desenvolvedores do core do Python trabalhando em um fork que tenta fazer uma “GILectomia”: eliminar o GIL completamente. Mas esse esforço vai demorar alguns anos ainda. https://github.com/larryhastings/gilectomy

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *