Estou escrevendo um aplicativo GUI que regularmente recupera dados por meio de uma conexão da web. Como essa recuperação demora um pouco, isso faz com que a IU pare de responder durante o processo de recuperação (não pode ser dividida em partes menores). É por isso que eu gostaria de terceirizar a conexão da web para um thread de trabalho separado.
[Sim, eu sei, agora tenho dois problemas .]
Enfim, o aplicativo usa PyQt4, então gostaria de saber qual é a melhor escolha: Usar threads do Qt ou usar o threading
módulo Python ? Quais são as vantagens / desvantagens de cada um? Ou você tem uma sugestão totalmente diferente?
Edit (re bounty): Embora a solução em meu caso particular provavelmente seja usar uma solicitação de rede sem bloqueio, como Jeff Ober e Lukáš Lalinský sugeriram (então, basicamente, deixando os problemas de simultaneidade para a implementação de rede), ainda gostaria de mais resposta aprofundada à questão geral:
Quais são as vantagens e desvantagens de usar threads de PyQt4 (ou seja, Qt) em vez de threads de Python nativos (do threading
módulo)?
Editar 2: Obrigado a todos por suas respostas. Embora não haja 100% de concordância, parece haver um consenso geral de que a resposta é "use Qt", já que a vantagem disso é a integração com o resto da biblioteca, embora não cause desvantagens reais.
Para qualquer um que queira escolher entre as duas implementações de threading, recomendo fortemente que leiam todas as respostas fornecidas aqui, incluindo o thread da lista de discussão PyQt para o qual o abade se vincula.
Houve várias respostas que considerei para a recompensa; no final, escolhi o abade para a referência externa muito relevante; foi, no entanto, uma decisão difícil.
Obrigado novamente.
fonte
QCoreApplication.postEvent
de um thread Python a uma taxa de 100 vezes por segundo, em um aplicativo que roda em plataforma cruzada e foi testado por 1000 horas. Eu nunca vi nenhum problema com isso. Acho que está tudo bem, desde que o objeto de destino esteja localizado no MainThread ou em um QThread. Eu também embrulhei em uma boa biblioteca, veja qtutils .Os threads do Python serão mais simples e seguros e, como se trata de um aplicativo baseado em I / O, eles são capazes de contornar o GIL. Dito isso, você considerou o I / O sem bloqueio usando sockets / select Twisted ou sem bloqueio?
EDIT: mais em tópicos
Tópicos Python
Os threads do Python são threads do sistema. No entanto, o Python usa um bloqueio de intérprete global (GIL) para garantir que o intérprete execute apenas um bloco de determinado tamanho de instruções de código de byte por vez. Felizmente, o Python libera o GIL durante as operações de entrada / saída, tornando os threads úteis para simular I / O sem bloqueio.
Advertência importante: isso pode ser enganoso, pois o número de instruções de código de byte não corresponde ao número de linhas em um programa. Mesmo uma única atribuição pode não ser atômica em Python, portanto, um bloqueio mutex é necessário para qualquer bloco de código que deve ser executado atomicamente, mesmo com o GIL.
Tópicos QT
Quando o Python transfere o controle para um módulo compilado de terceiros, ele libera o GIL. Torna-se responsabilidade do módulo garantir a atomicidade quando necessário. Quando o controle é passado de volta, Python usará o GIL. Isso pode tornar o uso de bibliotecas de terceiros em conjunto com threads confuso. É ainda mais difícil usar uma biblioteca de threading externa porque adiciona incerteza sobre onde e quando o controle está nas mãos do módulo versus o interpretador.
Threads QT operam com o GIL lançado. Threads QT são capazes de executar código de biblioteca QT (e outro código de módulo compilado que não adquire o GIL) simultaneamente. No entanto, o código Python executado dentro do contexto de uma thread QT ainda adquire o GIL, e agora você tem que gerenciar dois conjuntos de lógica para bloquear seu código.
No final, ambos os threads QT e Python são wrappers em torno dos threads do sistema. Threads Python são um pouco mais seguros de usar, já que as partes que não são escritas em Python (usando implicitamente o GIL) usam o GIL em qualquer caso (embora a advertência acima ainda se aplique).
E / S sem bloqueio
Threads adicionam complexidade extraordinária ao seu aplicativo. Especialmente ao lidar com a interação já complexa entre o interpretador Python e o código do módulo compilado. Embora muitos achem a programação baseada em eventos difícil de seguir, a E / S não bloqueadora baseada em eventos costuma ser muito menos difícil de raciocinar do que os threads.
Com a E / S assíncrona, você sempre pode ter certeza de que, para cada descritor aberto, o caminho de execução é consistente e ordenado. Existem, obviamente, questões que devem ser tratadas, como o que fazer quando o código dependendo de um canal aberto depende ainda mais dos resultados do código a ser chamado quando outro canal aberto retorna dados.
Uma boa solução para E / S sem bloqueio baseada em eventos é a nova biblioteca Diesel . Ele está restrito ao Linux no momento, mas é extraordinariamente rápido e bastante elegante.
Também vale a pena aprender pyevent , um invólucro da maravilhosa biblioteca libevent, que fornece uma estrutura básica para programação baseada em eventos usando o método mais rápido disponível para seu sistema (determinado em tempo de compilação).
fonte
A vantagem
QThread
é que ele está integrado com o resto da biblioteca Qt. Isto é, os métodos com reconhecimento de thread no Qt precisarão saber em qual thread eles rodam, e para mover objetos entre threads, você precisará usarQThread
. Outro recurso útil é executar seu próprio loop de eventos em um thread.Se você estiver acessando um servidor HTTP, você deve considerar
QNetworkAccessManager
.fonte
QNetworkAccessManager
parece promissor. Obrigado.Eu me perguntei a mesma coisa quando estava trabalhando no PyTalk .
Se você está usando Qt, você precisa
QThread
ser capaz de usar o framework Qt e especialmente o sistema de sinal / slot.Com o motor de sinal / slot, você será capaz de conversar de um tópico para outro e com todas as partes do seu projeto.
Além disso, não há muita questão de desempenho sobre essa escolha, uma vez que ambas são ligações C ++.
Aqui está minha experiência com PyQt e thread.
Eu encorajo você a usar
QThread
.fonte
Jeff tem alguns pontos positivos. Apenas um thread principal pode fazer atualizações de GUI. Se você precisar atualizar a GUI de dentro do thread, os sinais de conexão enfileirados do Qt-4 facilitam o envio de dados através dos threads e serão chamados automaticamente se você estiver usando QThread; Não tenho certeza se eles serão se você estiver usando threads Python, embora seja fácil adicionar um parâmetro a
connect()
.fonte
Também não posso recomendar, mas posso tentar descrever as diferenças entre os threads CPython e Qt.
Em primeiro lugar, os threads de CPython não são executados simultaneamente, pelo menos não o código Python. Sim, eles criam threads de sistema para cada thread do Python, no entanto, apenas a thread que contém o bloqueio global do intérprete pode ser executada (extensões C e código FFI podem contorná-lo, mas o bytecode do Python não é executado enquanto a thread não contém GIL).
Por outro lado, temos threads Qt, que são basicamente camadas comuns sobre threads do sistema, não têm Global Interpreter Lock e, portanto, são capazes de rodar simultaneamente. Não tenho certeza de como o PyQt lida com isso, no entanto, a menos que seus threads Qt chamem o código Python, eles devem ser capazes de executar simultaneamente (exceto vários bloqueios extras que podem ser implementados em várias estruturas).
Para um ajuste fino extra, você pode modificar a quantidade de instruções de bytecode que são interpretadas antes de trocar a propriedade de GIL - valores mais baixos significam mais troca de contexto (e possivelmente maior capacidade de resposta), mas desempenho inferior por thread individual (mudanças de contexto têm seu custo - se você tente mudar a cada poucas instruções, isso não ajuda a acelerar.)
Espero que ajude com seus problemas :)
fonte
Não posso comentar sobre as diferenças exatas entre tópicos Python e PyQt, mas eu tenho feito o que você está tentando fazer usando
QThread
,QNetworkAcessManager
e certificando-se de chamadaQApplication.processEvents()
enquanto o segmento está vivo. Se GUI capacidade de resposta é realmente o problema que está tentando resolver, o mais tarde vai ajudar.fonte
QNetworkAcessManager
não requer um tópico ouprocessEvents
. Ele usa operações de E / S assíncronas.QNetworkAcessManager
ehttplib2
. Meu código assíncrono usahttplib2
.