76 votes

Threading en Python

Quels sont les modules utilisés pour écrire des applications multithread en Python ? Je suis conscient des mécanismes de concurrence de base fournis par le langage et aussi de l'existence des modules suivants Python sans queue ni tête mais quelles sont leurs forces et leurs faiblesses respectives ?

0 votes

Je vois au moins trois raisons pour lesquelles cette question (opinion, recommandation et trop large) doit être mise en attente.

1 votes

Je sais que cette question date de 09 mais quelqu'un peut-il y répondre avec asyncio s'il vous plaît ? Merci d'avance et j'aimerais bien voir à quoi ressemblerait le code.

0 votes

@lpapp où la question a généré quelques réponses d'opinion, je crois que la formulation de la question est objective (trop large, peut-être). Le PO demande quels modules sont utilisés en Python pour la concurrence, et les avantages et inconvénients de chacun. Cela me semble raisonnable.

117voto

quark Points 7773

Par ordre de complexité croissante :

Utilisez le module d'enfilage

Pour :

  • Il est très facile d'exécuter n'importe quelle fonction (n'importe quel appelable en fait) dans sa section propre thread.
  • Le partage des données est sinon facile (les verrous ne sont jamais faciles :), du moins simple. au moins simple.

Cons :

  • Comme indiqué par Juergen Les threads Python ne peuvent pas accéder simultanément à l'état de l'interpréteur (il y a un gros verrou, le tristement célèbre Verrouillage de l'interprète global .) En pratique, cela signifie que les threads sont utiles pour les tâches liées aux entrées/sorties (mise en réseau, écriture sur disque, etc.), mais pas du tout pour effectuer des calculs simultanés.

Utilisez le multitraitement module

Dans le cas d'une utilisation simple, cela revient exactement à utiliser threading sauf que chaque tâche est exécutée dans son propre processus et non dans son propre thread. (Presque littéralement : Si vous prenez L'exemple d'Eli et remplacer threading con multiprocessing , Thread avec Process y Queue (le module) avec multiprocessing.Queue il devrait fonctionner sans problème).

Pour :

  • Concurrence réelle pour toutes les tâches (pas de verrouillage global de l'interpréteur).
  • Il s'adapte à plusieurs processeurs, et peut même s'adapter à plusieurs ordinateurs. machines .

Cons :

  • Les processus sont plus lents que les threads.
  • Le partage de données entre processus est plus délicat qu'avec les threads.
  • La mémoire n'est pas implicitement partagée. Vous devez soit la partager explicitement, soit récupérer des variables et les envoyer dans les deux sens. C'est plus sûr, mais plus difficile. (Si cela importe de plus en plus, les développeurs de Python semblent pousser les gens dans cette direction).

Utilisez un modèle d'événement, tel que Torsadé

Pour :

  • Vous disposez d'un contrôle extrêmement fin sur la priorité, sur ce qui est exécuté et quand.

Cons :

  • Même avec une bonne bibliothèque, la programmation asynchrone est généralement plus difficile que la programmation threadée, à la fois en termes de compréhension de ce qui est censé se passer et en termes de débogage de ce qui se passe réellement.

En todo Je suppose que vous comprenez déjà de nombreux problèmes liés au multitâche, notamment la question délicate du partage des données entre les tâches. Si, pour une raison quelconque, vous ne savez pas quand et comment utiliser les verrous et les conditions, vous devez commencer par ces éléments. Le code multitâche est plein de subtilités et de pièges, et il est vraiment préférable d'avoir une bonne compréhension des concepts avant de commencer.

4 votes

Je dirais que votre ordre de complexité est presque entièrement inversé. La programmation multithread est vraiment difficile à faire correctement (presque personne ne le fait). La programmation événementielle est différente, mais c'est vraiment facile de comprendre ce qui se passe et d'écrire des tests qui prouvent qu'il fait ce qu'il doit faire. (Je dis cela après avoir atteint une couverture de 100% sur une bibliothèque réseau massivement concurrente ce week-end).

3 votes

Hmm. Je pense que la programmation d'événements expose la complexité. Cela vous oblige à l'aborder plus directement. Vous pouvez soutenir que la complexité est inhérente à la concurrence, quelle que soit la façon dont vous l'abordez, et je serais d'accord avec vous. Mais après avoir réalisé des programmes assez importants basés sur des threads et des événements, je pense que je maintiens ce que j'ai dit : le programme basé sur des événements était beaucoup plus sous mon contrôle, mais il était plus complexe de le coder réellement.

1 votes

Qu'est-ce que Les processus sont plus lents que les threads. signifie exactement ? Les processus ne peuvent pas être plus lents que les threads, car les processus n'exécutent pas de code. Les threads dans ces processus exécutent le code. Je ne peux que supposer que cela signifie que démarrage est plus lent, ce qui est correct. Donc, si votre programme a besoin de démarrer fréquemment de nouveaux threads/processus, l'utilisation de la fonction multiprocessing serait plus lent. Dans ce cas, cependant, votre utilisation du multithreading pourrait également être améliorée de manière significative.

103voto

Alex Martelli Points 330805

Vous avez déjà reçu une grande variété de réponses, allant des "faux fils" aux cadres externes, mais je n'ai vu personne mentionner Queue.Queue -- la "sauce secrète" du threading de CPython.

Pour développer : tant que vous n'avez pas besoin de superposer des traitements lourds pour le CPU en Python pur (dans ce cas, vous avez besoin de multiprocessing -- mais ça vient avec ses propres Queue aussi, donc vous pouvez, avec quelques précautions nécessaires, appliquer les conseils généraux que je donne;-), l'implémentation intégrée de Python threading fera l'affaire... mais il le fera bien mieux si vous l'utilisez à bon escient par exemple, comme suit.

"Oubliez la mémoire partagée, censée être le principal avantage du threading par rapport au multiprocessing - elle ne fonctionne pas bien, ne s'adapte pas bien, n'a jamais fonctionné et ne fonctionnera jamais. N'utilisez la mémoire partagée que pour les structures de données qui sont mises en place une seule fois. avant vous créez des sous-filières et ne changez plus rien par la suite - pour tout le reste, créez une simple responsable de cette ressource, et communiquer avec ce thread par l'intermédiaire de Queue .

Consacrez un thread spécialisé à chaque ressource que vous penseriez normalement protéger par des verrous : une structure de données mutable ou un groupe cohésif de celles-ci, une connexion à un processus externe (une BD, un serveur XMLRPC, etc.), un fichier externe, etc. Mettez en place un petit pool de threads pour les tâches d'usage général qui n'ont pas ou n'ont pas besoin d'une ressource dédiée de ce type Ne le fais pas. Créez des threads au fur et à mesure de vos besoins, sinon vous serez submergé par la surcharge du changement de thread.

La communication entre deux threads se fait toujours via Queue.Queue -- une forme de passage de messages, la seule base saine pour le multitraitement (à part la mémoire transactionnelle, qui est prometteuse mais pour laquelle je ne connais aucune implémentation digne de ce nom, sauf en Haskell).

Chaque thread dédié gérant une seule ressource (ou un petit ensemble cohésif de ressources) écoute les demandes sur une instance spécifique de Queue.Queue. Les threads d'un pool attendent sur une seule instance partagée de Queue.Queue (Queue est solidement threadsafe et ne le fera pas vous faire défaut en la matière).

Les threads qui ont juste besoin de mettre en file d'attente une requête sur une file d'attente (partagée ou dédiée) le font sans attendre de résultats, et passent à autre chose. Les threads qui ont éventuellement besoin d'un résultat ou d'une confirmation pour une requête mettent en file d'attente une paire (requête, file d'attente de réception) avec une instance de Queue.Queue qu'ils viennent de créer, et finalement, quand la réponse ou la confirmation est indispensable pour continuer, ils obtiennent (en attendant) de leur file d'attente de réception. Assurez-vous d'être prêt à recevoir des réponses d'erreur ainsi que de vraies réponses ou confirmations (la fonction deferred sont excellents pour organiser ce type de réponse structurée, d'ailleurs !).

Vous pouvez également utiliser la file d'attente pour "parquer" des instances de ressources qui peuvent être utilisées par n'importe quel thread mais qui ne peuvent jamais être partagées entre plusieurs threads à la fois (connexions DB avec certains composants DBAPI, curseurs avec d'autres, etc) -- cela vous permet de relâcher l'exigence de threads dédiés en faveur d'une plus grande mise en commun (un thread de pool qui reçoit de la file d'attente partagée une requête nécessitant une ressource pouvant être mise en file d'attente obtiendra cette ressource de la file d'attente appropriée, en attendant si nécessaire, etc etc).

Twisted est en fait un bon moyen d'organiser ce menuet (ou cette danse carrée, selon le cas), non seulement grâce aux deferreds, mais aussi en raison de son architecture de base saine, solide et hautement évolutive : vous pouvez organiser les choses de manière à utiliser des threads ou des sous-processus uniquement lorsque cela est vraiment justifié, tout en faisant la plupart des choses normalement considérées comme méritant un thread dans un seul thread événementiel.

Mais je réalise que Twisted n'est pas pour tout le monde - l'approche "dédier ou mettre en commun des ressources, utiliser les files d'attente à fond, ne jamais faire quoi que ce soit qui nécessite un verrou ou, à l'insu de Guido, une procédure de synchronisation encore plus avancée, comme un sémaphore ou une condition" peut toujours être utilisée même si vous ne pouvez pas vous faire à l'idée des méthodologies événementielles asynchrones, et elle offrira toujours plus de fiabilité et de performance que toute autre approche de threading largement applicable sur laquelle je suis tombé.

6 votes

Si vous pouviez préférer une réponse, je le ferais pour celle-ci. C'est l'une des réponses les plus stimulantes que j'ai trouvées sur Stack Overflow.

1 votes

@quark, merci pour les mots gentils, et, content que vous l'ayez aimé !

4 votes

J'aimerais pouvoir mettre deux fois plus de votes que le précédent.

22voto

Eli Courtwright Points 53071

Cela dépend de ce que vous essayez de faire, mais j'ai un faible pour l'utilisation de la fonction threading dans la bibliothèque standard, car il permet de prendre facilement n'importe quelle fonction et de l'exécuter dans un thread séparé.

from threading import Thread

def f():
    ...

def g(arg1, arg2, arg3=None):
    ....

Thread(target=f).start()
Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start()

Et ainsi de suite. J'ai souvent une configuration producteur/consommateur qui utilise une file d'attente synchronisée fournie par le système de gestion de l'information. Queue module

from Queue import Queue
from threading import Thread

q = Queue()
def consumer():
    while True:
        print sum(q.get())

def producer(data_source):
    for line in data_source:
        q.put( map(int, line.split()) )

Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start()
for i in range(10):
    Thread(target=consumer).start()

1 votes

Je suis désolé, si je veux passer un callable retournant une valeur comme cible à un Thread, comment puis-je récupérer son résultat dans le thread principal ? Est-ce possible ou dois-je utiliser un wrapper pour la fonction en mettant son résultat dans un objet modifiable ? Je ne voudrais pas lier le résultat à l'objet thread lui-même. Quelle est la meilleure pratique ? Je vous remercie.

3 votes

@newtover : Vous décrivez toujours la même situation de base de threading producteur/consommateur que dans mon exemple, donc dans ce cas la solution Pythonique est toujours d'utiliser un objet Queue synchronisé. Demandez à chaque thread de placer son résultat dans la file d'attente des valeurs de sortie, et demandez au thread principal de les récupérer de la file d'attente à son gré. La documentation de la classe Queue se trouve à l'adresse suivante docs.python.org/library/queue.html et ils ont même un exemple de ce que vous décrivez à l'adresse suivante docs.python.org/library/queue.html#Queueueue.Queue.join

1 votes

Merci pour le lien et la réponse. Une autre question : existe-t-il quelque chose qui ressemble à un dictionnaire avec la même fonctionnalité, ou est-il préférable de récupérer tous les éléments de la file d'attente et de remplir le dictionnaire moi-même ? Puis-je utiliser le dictionnaire intégré à cette fin, en joignant moi-même les fils ?

13voto

Sam Hasler Points 10253

Kamaelia est un cadre python pour la création d'applications comportant de nombreux processus communicants.

(source : <a href="http://www.kamaelia.org/cat-trans-medium.png" rel="nofollow noreferrer">kamaelia.org </a>) Kamaelia - La concussion rendue utile et amusante

Dans Kamaelia, vous construisez des systèmes à partir des composants simples qui communiquent entre eux . Cela accélère le développement, facilite considérablement la maintenance et signifie également que vous créer des logiciels naturellement concurrents . Il est destiné à être accessible par tout développeur, y compris les novices. Cela permet également de s'amuser :)

Quels types de systèmes ? Des serveurs de réseau, des clients, des applications de bureau, des jeux basés sur pygame, des systèmes de transcodage et des pipelines, des systèmes de télévision numérique, des éradicateurs de spam, des outils d'enseignement, et bien d'autres choses encore :)

Voici une vidéo de Pycon 2009. Elle commence par comparer Kamaelia à Torsadé y Python parallèle et fait ensuite une démonstration pratique de Kamaelia.

Concurrence facile avec Kamaelia - Partie 1 (59:08)
Concurrence facile avec Kamaelia - Partie 2 (18:15)

1 votes

Je ne sais pas exactement pourquoi quelqu'un aurait noté cette réponse... inversez le vote s'il vous plaît... à moins que vous puissiez fournir une bonne raison pour la noter...

15 votes

Je suppose que certaines personnes détestent les chats

6voto

Michael Sparks Points 411

En ce qui concerne Kamaelia, la réponse ci-dessus ne couvre pas vraiment l'avantage ici. L'approche de Kamaelia fournit une interface unifiée, qui est pragmatique mais pas parfaite, pour traiter les threads, les générateurs et les processus dans un système unique pour la concurrence.

Fondamentalement, il s'agit de la métaphore d'une chose en cours qui a des boîtes de réception et des boîtes de sortie. Vous envoyez des messages aux boîtes de sortie, et lorsqu'ils sont connectés ensemble, les messages circulent des boîtes de sortie aux boîtes de réception. Cette métaphore/API reste la même, que vous utilisiez des générateurs, des threads ou des processus, ou que vous vous adressiez à d'autres systèmes.

La partie "pas parfaite" est due au fait que le sucre syntaxique n'a pas encore été ajouté pour les boîtes de réception et les boîtes d'envoi (bien que cela soit en cours de discussion) - le système est axé sur la sécurité et la facilité d'utilisation.

En reprenant l'exemple producteur-consommateur utilisant le bare threading ci-dessus, cela devient ceci dans Kamaelia :

Pipeline(Producer(), Consumer() )

Dans cet exemple, il n'est pas important de savoir s'il s'agit de composants threadés ou non, la seule différence entre eux du point de vue de l'utilisation est la classe de base du composant. Les composants générateurs communiquent en utilisant des listes, les composants threadés en utilisant Queue.Queues et les composants basés sur des processus en utilisant os.pipes.

La raison de cette approche est de rendre plus difficile le débogage des bogues. Dans le threading - ou toute autre concurrence à mémoire partagée, le problème numéro un auquel vous êtes confronté est la mise à jour accidentelle des données partagées. En utilisant le passage de messages, vous éliminez un classe de bogues.

Si vous utilisez des threads nus et des verrous partout, vous partez généralement du principe que lorsque vous écrivez du code, vous ne ferez pas d'erreur. Bien que nous aspirions tous à cela, il est très rare que cela se produise. En regroupant le comportement de verrouillage en un seul endroit, vous simplifiez les endroits où les choses peuvent mal tourner. (Les gestionnaires de contexte sont utiles, mais n'aident pas en cas de mises à jour accidentelles en dehors du gestionnaire de contexte).

Il est évident que tous les morceaux de code ne peuvent pas être écrits avec un style de passage de messages et un style partagé, c'est pourquoi Kamaelia dispose également d'une mémoire transactionnelle logicielle (STM), qui est une idée vraiment intéressante mais dont le nom n'est pas très élégant - il s'agit plutôt d'un contrôle de version pour les variables - c'est-à-dire qu'il faut extraire certaines variables, les mettre à jour et les renvoyer. Si vous obtenez un conflit, vous rincez et répétez.

Liens pertinents :

Quoi qu'il en soit, j'espère que c'est une réponse utile. Pour information, la raison principale de la configuration de Kamaelia est de rendre la concurrence plus sûre et plus facile à utiliser dans les systèmes python, sans que la queue ne remue le chien (c'est-à-dire le grand seau de composants).

Je peux comprendre pourquoi l'autre réponse de Kamaelia a été modérée, car même pour moi, elle ressemble plus à une publicité qu'à une réponse. En tant qu'auteur de Kamaelia, c'est agréable de voir de l'enthousiasme, mais j'espère que cette réponse contiendra un contenu un peu plus pertinent :-)

Et c'est ma façon de dire, s'il vous plaît prendre la mise en garde que cette réponse est par définition biaisée, mais pour moi, l'objectif de Kamaelia est d'essayer d'envelopper ce qui est IMO meilleure pratique. Je vous suggère d'essayer plusieurs systèmes et de voir lequel vous convient le mieux. (Si cette question n'est pas appropriée pour le débordement de pile, je suis désolé - je suis nouveau sur ce forum :-)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X