O Python oferece suporte a multithreading? Ele pode acelerar o tempo de execução?

95

Estou um pouco confuso sobre se o multithreading funciona ou não em Python.

Sei que tem havido muitas perguntas sobre isso e li muitas delas, mas ainda estou confuso. Sei por experiência própria e vi outras pessoas postarem suas próprias respostas e exemplos aqui no StackOverflow que o multithreading é realmente possível em Python. Então, por que todo mundo diz que o Python está bloqueado pelo GIL e que apenas um thread pode ser executado por vez? Claramente funciona. Ou há alguma distinção que não estou entendendo aqui?

Muitos participantes / participantes também mencionam que o threading é limitado porque não faz uso de vários núcleos. Mas eu diria que eles ainda são úteis porque funcionam simultaneamente e, portanto, fazem a carga de trabalho combinada mais rapidamente. Quero dizer, por que haveria mesmo um módulo de thread Python caso contrário?

Atualizar:

Obrigado por todas as respostas até agora. Pelo que entendi, o multithreading só será executado em paralelo para algumas tarefas de E / S, mas só pode ser executado um de cada vez para várias tarefas principais vinculadas à CPU.

Não tenho certeza do que isso significa para mim em termos práticos, então darei apenas um exemplo do tipo de tarefa que gostaria de multithread. Por exemplo, digamos que eu queira percorrer uma lista muito longa de strings e fazer algumas operações básicas com strings em cada item da lista. Se eu dividir a lista, enviar cada sublista para ser processada por meu código de loop / string em um novo encadeamento e enviar os resultados de volta em uma fila, essas cargas de trabalho serão executadas aproximadamente ao mesmo tempo? Mais importante, isso teoricamente irá acelerar o tempo que leva para executar o script?

Outro exemplo poderia ser se eu pudesse renderizar e salvar quatro imagens diferentes usando PIL em quatro threads diferentes e fazer com que isso fosse mais rápido do que processar as imagens uma a uma após a outra? Acho que esse componente de velocidade é o que realmente estou pensando, e não qual é a terminologia correta.

Eu também sei sobre o módulo de multiprocessamento, mas meu principal interesse agora é para cargas de tarefas pequenas a médias (10-30 segundos) e, portanto, acho que multithreading será mais apropriado porque os subprocessos podem demorar para iniciar.

Karim Bahgat
fonte
4
Esta é uma questão bastante carregada. Acho que a resposta está no que você quer que os fios façam. Na maioria das circunstâncias, o GIL impede que mais de 1 thread seja executado simultaneamente. No entanto, existem alguns casos em que o GIL é liberado (por exemplo, leitura de um arquivo) para que isso possa ser feito em paralelo. Observe também que o GIL é um detalhe de implementação do Cpython (a implementação mais comum). Nenhuma outra implementação de python (Jython, PyPy, etc) tem um GIL (AFAIK)
mgilson
2
@mgilson PyPy tem um GIL.
2
@delnan - Você parece estar correto. Obrigado.
mgilson
1
"os subprocessos podem ser lentos para iniciar" - você pode criar um conjunto de tarefas prontas para execução. A sobrecarga pode ser limitada a aproximadamente o tempo necessário para serializar / desserializar os dados necessários para que a tarefa comece a funcionar.
Brian Cain
1
@KarimBahgat, é exatamente o que quero dizer.
Brian Cain

Respostas:

132

O GIL não impede o threading. Tudo o que o GIL faz é certificar-se de que apenas um thread está executando o código Python por vez; o controle ainda alterna entre os threads.

O que o GIL impede então, é fazer uso de mais de um núcleo de CPU ou CPUs separadas para rodar threads em paralelo.

Isso se aplica apenas ao código Python. As extensões C podem liberar o GIL para permitir que vários threads de código C e um thread Python sejam executados em vários núcleos. Isso se estende à E / S controlada pelo kernel, como select()chamadas para leituras e gravações de soquete, fazendo com que o Python manipule eventos de rede de maneira razoavelmente eficiente em uma configuração multi-core multi-thread.

O que muitas implantações de servidor fazem é executar mais de um processo Python, para permitir que o sistema operacional lide com a programação entre os processos para utilizar seus núcleos de CPU ao máximo. Você também pode usar a multiprocessingbiblioteca para lidar com o processamento paralelo em vários processos de uma base de código e processo pai, se for adequado aos seus casos de uso.

Observe que o GIL é aplicável apenas à implementação CPython; Jython e IronPython usam uma implementação de threading diferente (os threads de tempo de execução comuns Java VM e .NET, respectivamente).

Para abordar sua atualização diretamente: Qualquer tarefa que tentar obter um aumento de velocidade da execução paralela, usando código Python puro, não terá uma aceleração, pois o código Python encadeado está bloqueado para um encadeamento em execução por vez. Se você misturar extensões C e E / S, no entanto (como PIL ou operações numpy), qualquer código C pode ser executado em paralelo com um thread Python ativo.

O threading Python é ótimo para criar uma GUI responsiva ou para lidar com várias solicitações curtas da web em que I / O é o gargalo mais do que o código Python. Não é adequado para paralelizar código Python de computação intensiva, siga o multiprocessingmódulo para tais tarefas ou delegue a uma biblioteca externa dedicada.

Martijn Pieters
fonte
Obrigado @MartijnPieters, então tenho uma resposta mais clara para a minha pergunta se o threading pode ser usado para acelerar o código, como um loop for, que é "não". Talvez você ou alguém pudesse escrever uma nova resposta que eu possa aceitar que forneça alguns exemplos específicos de módulos / códigos / operações comuns onde o threading será permitido pelo GIL para executar paralelamente e, portanto, mais rápido (por exemplo, exemplos de I / O e rede / as operações de leitura de soquete que foram mencionadas e quaisquer outros casos onde multithreading em Python é útil). Talvez uma boa lista de usos comuns de multithread e alguns exemplos de programação, se possível?
Karim Bahgat
4
Não, não acho que essa resposta seria muito útil; para ser honesto. Você não pode criar uma lista exaustiva, nunca, mas a regra é que qualquer I / O (leitura e gravação de arquivos, sockets de rede, canais) é tratada em C, e muitas bibliotecas C também liberam o GIL para seus operações, mas cabe às bibliotecas documentar isso para você.
Martijn Pieters
1
Que pena, não vi sua resposta atualizada até agora, onde você deu alguns bons exemplos de uso de thread. Isso incluía (corrija-me se eu estiver errado) programação de rede (por exemplo urllib.urlopen()?), Para chamar um script Python de dentro de uma GUI Python e chamar várias operações PIL (por exemplo Image.transform()) e numpy (por exemplo numpy.array()) com threads. E você forneceu mais alguns exemplos em seu comentário, como o uso de vários tópicos para ler arquivos (por exemplo f.read(),?). Sei que uma lista exaustiva não é possível, só queria os tipos de exemplos que você deu em sua atualização. De qualquer forma, aceitei sua resposta :)
Karim Bahgat
2
@KarimBahgat: Sim, urllib.urlopen()chamaria os soquetes de rede, aguardar a E / S do soquete é uma excelente oportunidade para alternar threads e fazer outra coisa.
Martijn Pieters
4
Embora não seja diretamente relevante para esse problema, é importante notar que às vezes o threading não tem nada a ver com desempenho; pode ser mais simples escrever seu código como vários threads independentes de execução. Por exemplo, você pode ter um thread tocando música de fundo, um atendendo a IU e outro trabalhando em cálculos que devem ser feitos eventualmente, mas não estão com pressa. Tentar reproduzir em sequência o próximo buffer de áudio com o runloop da IU ou quebrar sua computação em pedaços pequenos o suficiente para não interferir na interatividade pode ser muito mais difícil do que usar threads.
abarnert
4

Sim. :)

Você tem o baixo nível fio módulo e o nível mais elevado de threading módulo. Mas se você simplesmente deseja usar máquinas multicore, o módulo de multiprocessamento é o caminho a percorrer.

Citação dos documentos :

No CPython, devido ao Global Interpreter Lock, apenas um thread pode executar o código Python de uma vez (embora certas bibliotecas orientadas para desempenho possam superar essa limitação). Se você deseja que seu aplicativo faça melhor uso dos recursos computacionais de máquinas com vários núcleos, é recomendável usar o multiprocessamento. No entanto, o encadeamento ainda é um modelo apropriado se você deseja executar várias tarefas associadas a E / S simultaneamente.

zord
fonte
3

Threading é permitido em Python, o único problema é que o GIL irá garantir que apenas um thread seja executado por vez (sem paralelismo).

Então, basicamente, se você quiser multi-thread o código para acelerar o cálculo, ele não vai acelerar, pois apenas uma thread é executada por vez, mas se você usá-lo para interagir com um banco de dados, por exemplo, ele o fará.

r.guerbab
fonte