web-dev-qa-db-fra.com

Threading dans une application PyQt: utilisez des threads Qt ou des threads Python?

J'écris une application graphique qui récupère régulièrement des données via une connexion Web. Étant donné que cette récupération prend un certain temps, cela empêche l'interface utilisateur de répondre pendant le processus de récupération (elle ne peut pas être divisée en parties plus petites). C'est pourquoi je voudrais sous-traiter la connexion Web à un thread de travail distinct.

[Oui, je sais, maintenant j'ai deux problèmes .]

Quoi qu'il en soit, l'application utilise PyQt4, donc j'aimerais savoir quel est le meilleur choix: utilisez les threads de Qt ou utilisez le module Python threading? Quels sont les avantages/inconvénients de Ou avez-vous une suggestion totalement différente?

Edit (re bounty): Alors que la solution dans mon cas particulier utilisera probablement une requête réseau non bloquante comme Jeff Ober = et Lukáš Lalinský suggéré (laissant donc essentiellement les problèmes de concurrence à l'implémentation du réseau), j'aimerais toujours une réponse plus approfondie à la question générale:

Quels sont les avantages et les inconvénients de l'utilisation des threads de PyQt4 (c'est-à-dire Qt) sur les threads natifs Python (du module threading)?


Edit 2: Merci à tous pour vos réponses. Bien qu'il n'y ait pas d'accord à 100%, il semble y avoir un large consensus sur le fait que la réponse est "utiliser Qt", car l'avantage de cela est l'intégration avec le reste de la bibliothèque, sans causer de réels inconvénients.

Pour ceux qui cherchent à choisir entre les deux implémentations de threading, je leur recommande fortement de lire toutes les réponses fournies ici, y compris le thread de la liste de diffusion PyQt auquel abbot renvoie.

Il y avait plusieurs réponses que j'ai envisagées pour la prime; à la fin j'ai choisi l'abbé pour la référence externe très pertinente; ce fut cependant un appel serré.

Merci encore.

104
balpha

C'était discuté il n'y a pas si longtemps dans la liste de diffusion PyQt. Citant Giovanni Bajo commentaires sur le sujet:

C'est à peu près la même chose. La principale différence est que les QThreads sont mieux intégrés à Qt (signaux/slots asynchrones, boucle d'événement, etc.). De plus, vous ne pouvez pas utiliser Qt à partir d'un thread Python (vous ne pouvez par exemple pas publier d'événement sur le thread principal via QApplication.postEvent): vous avez besoin d'un QThread pour que cela fonctionne.

Une règle générale pourrait être d'utiliser QThreads si vous allez interagir d'une manière ou d'une autre avec Qt, et d'utiliser les threads Python sinon.

Et certains commentaires antérieurs à ce sujet de l'auteur de PyQt: "ils sont tous les deux des wrappers autour des mêmes implémentations de thread natif". Et les deux implémentations utilisent GIL de la même manière.

99
abbot

Les threads de Python seront plus simples et plus sûrs, et comme c'est pour une application basée sur les E/S, ils sont capables de contourner le GIL. Cela dit, avez-vous envisagé des E/S non bloquantes en utilisant des connecteurs/sélection torsadés ou non bloquants?

EDIT: plus sur les threads

Fils Python

Les threads de Python sont des threads système. Cependant, Python utilise un verrou d'interpréteur global (GIL) pour garantir que l'interpréteur n'exécute jamais qu'un certain bloc d'instructions de code octet à la fois. Heureusement, Python libère le GIL pendant les opérations d'entrée/sortie, ce qui rend les threads utiles pour simuler les E/S non bloquantes.

Avertissement important: Cela peut être trompeur, car le nombre d'instructions de code octet pas correspond au nombre de lignes dans un programme. Même une seule affectation peut ne pas être atomique en Python, donc un verrou mutex est nécessaire pour n'importe quel bloc de code qui doit être exécuté atomiquement, même avec le GIL.

Fils QT

Lorsque Python transfère le contrôle à un module compilé tiers, il libère le GIL. Il incombe au module de garantir l'atomicité là où cela est requis. Lorsque le contrôle est renvoyé, Python utilisera le GIL. Cela peut rendre l'utilisation de bibliothèques tierces conjointement avec des threads déroutante. Il est encore plus difficile d'utiliser une bibliothèque de threads externe car cela ajoute une incertitude quant à l'endroit et au moment où le contrôle est entre les mains du module vs l'interprète.

Les threads QT fonctionnent avec le GIL libéré. Les threads QT peuvent exécuter le code de bibliothèque QT (et tout autre code de module compilé qui n'acquiert pas le GIL) simultanément. Cependant, le Python exécuté dans le contexte d'un thread QT encore acquiert le GIL, et maintenant vous devez gérer deux ensembles de logique pour verrouiller votre code.

En fin de compte, les threads QT et les threads Python sont des enveloppes autour des threads système. Python sont légèrement plus sûrs à utiliser, car les parties qui ne sont pas écrites Python (en utilisant implicitement le GIL) utilisez le GIL dans tous les cas (bien que la mise en garde ci-dessus s'applique toujours.)

E/S non bloquantes

Les threads ajoutent une complexité extraordinaire à votre application. Surtout lorsqu'il s'agit de l'interaction déjà complexe entre l'interpréteur Python et le code de module compilé. Bien que beaucoup trouvent la programmation basée sur les événements difficile à suivre, les E/S non bloquantes basées sur les événements sont souvent beaucoup moins difficile à raisonner que les fils.

Avec les E/S asynchrones, vous pouvez toujours être sûr que, pour chaque descripteur ouvert, le chemin d'exécution est cohérent et ordonné. Il y a, évidemment, des problèmes qui doivent être résolus, comme quoi faire lorsque le code dépendant d'un canal ouvert dépend en outre des résultats du code à appeler lorsqu'un autre canal ouvert renvoie des données.

Une nouvelle solution intéressante pour les E/S non bloquantes basées sur les événements est la nouvelle bibliothèque Diesel . Il est limité à Linux pour le moment, mais il est extraordinairement rapide et assez élégant.

Cela vaut également la peine d'apprendre pyevent , un wrapper autour de la merveilleuse bibliothèque libevent, qui fournit un cadre de base pour la programmation événementielle en utilisant la méthode disponible la plus rapide pour votre système (déterminée au moment de la compilation).

32
Jeff Ober

L'avantage de QThread est qu'il est intégré au reste de la bibliothèque Qt. Autrement dit, les méthodes sensibles aux threads dans Qt devront savoir dans quel thread elles s'exécutent et pour déplacer des objets entre les threads, vous devrez utiliser QThread. Une autre fonctionnalité utile consiste à exécuter votre propre boucle d'événements dans un thread.

Si vous accédez à un serveur HTTP, vous devez envisager QNetworkAccessManager.

21
Lukáš Lalinský

Je me suis posé la même question quand je travaillais pour PyTalk .

Si vous utilisez Qt, vous devez utiliser QThread pour pouvoir utiliser le framework Qt et en particulier le système signal/slot.

Avec le signal/slot engine, vous pourrez parler d'un fil à l'autre et avec chaque partie de votre projet.

De plus, ce choix n'est pas très question de performances car les deux sont des liaisons C++.

Voici mon expérience de PyQt et de thread.

Je vous encourage à utiliser QThread.

13
Natim

Jeff a de bons points. Un seul thread principal peut effectuer des mises à jour de l'interface graphique. Si vous devez mettre à jour l'interface graphique à partir du thread, les signaux connexion en file d'attente de Qt-4 facilitent l'envoi de données entre les threads et seront automatiquement invoqués si vous utilisez QThread; Je ne sais pas s'ils le seront si vous utilisez des threads Python, bien qu'il soit facile d'ajouter un paramètre à connect().

9
Kaleb Pederson

Je ne peux pas vraiment recommander non plus, mais je peux essayer de décrire les différences entre les threads CPython et Qt.

Tout d'abord, les threads CPython ne s'exécutent pas simultanément, du moins pas Python. Oui, ils créent des threads système pour chaque Python, mais seul le le thread qui détient actuellement Global Interpreter Lock est autorisé à s'exécuter (les extensions C et le code FFI peuvent le contourner, mais Python n'est pas exécuté alors que le thread ne contient pas GIL).

D'un autre côté, nous avons des threads Qt, qui sont fondamentalement une couche commune sur les threads système, n'ont pas Global Interpreter Lock et sont donc capables de s'exécuter simultanément. Je ne sais pas comment PyQt le gère, cependant, à moins que vos threads Qt n'appellent Python, ils devraient pouvoir s'exécuter simultanément (à l'exception de divers verrous supplémentaires qui pourraient être implémentés dans diverses structures).

Pour un réglage plus fin, vous pouvez modifier la quantité d'instructions de bytecode qui sont interprétées avant de changer de propriétaire de GIL - des valeurs inférieures signifient plus de changement de contexte (et éventuellement une réactivité plus élevée) mais des performances inférieures par thread individuel (les commutateurs de contexte ont leur coût - si vous essayez de changer toutes les quelques instructions, cela n'aide pas à accélérer.)

J'espère que cela vous aidera avec vos problèmes :)

5
p_l

Je ne peux pas commenter les différences exactes entre les threads Python et PyQt, mais j'ai fait ce que vous essayez de faire en utilisant QThread, QNetworkAcessManager et en veillant à appeler QApplication.processEvents() pendant que le thread est vivant. Si la réactivité de l'interface graphique est vraiment le problème que vous essayez de résoudre, la dernière vous aidera.

0
brianz