Comment les threads fonctionnent-ils en Python et quels sont les pièges courants spécifiques aux threads Python?

85

J'ai essayé de comprendre comment les threads fonctionnent en Python, et il est difficile de trouver de bonnes informations sur leur fonctionnement. Il me manque peut-être un lien ou quelque chose, mais il semble que la documentation officielle ne soit pas très complète sur le sujet, et je n'ai pas été en mesure de trouver une bonne rédaction.

D'après ce que je peux dire, un seul thread peut être exécuté à la fois et le thread actif change toutes les 10 instructions environ?

Où y a-t-il une bonne explication, ou pouvez-vous en fournir une? Il serait également très agréable d'être conscient des problèmes courants que vous rencontrez lors de l'utilisation de threads avec Python.

jdd
la source

Réponses:

50

Oui, en raison du verrouillage global de l'interpréteur (GIL), il ne peut exécuter qu'un seul thread à la fois. Voici quelques liens avec quelques idées à ce sujet:

Du dernier lien une citation intéressante:

Laissez-moi vous expliquer ce que cela signifie. Les threads s'exécutent dans la même machine virtuelle, et donc s'exécutent sur la même machine physique. Les processus peuvent s'exécuter sur la même machine physique ou sur une autre machine physique. Si vous concevez votre application autour de threads, vous n'avez rien fait pour accéder à plusieurs machines. Ainsi, vous pouvez vous adapter à autant de cœurs sur une seule machine (ce qui sera assez peu au fil du temps), mais pour vraiment atteindre les échelles Web, vous devrez quand même résoudre le problème de plusieurs machines.

Si vous souhaitez utiliser le multicœur, pyprocessing définit une API basée sur des processus pour effectuer une réelle parallélisation. Le PEP comprend également des repères intéressants.

Peter Hoffmann
la source
1
Vraiment un commentaire sur la citation de smoothspan: sûrement le threading Python vous limite effectivement à un noyau, même si la machine en a plusieurs? Le multicœur peut présenter des avantages car le prochain thread peut être prêt à fonctionner sans changement de contexte, mais vos threads Python ne peuvent jamais utiliser> 1 cœur à la fois.
James Brady
2
Correct, les threads python sont pratiquement limités à un seul noyau, à moins qu'un module C n'interagisse bien avec le GIL et exécute son propre thread natif.
Arafangion
En fait, plusieurs cœurs rendent les threads moins efficaces car il y a beaucoup de désabonnement avec la vérification si chaque thread peut accéder au GIL. Même avec le nouveau GIL, les performances sont encore pires ... dabeaz.com/python/NewGIL.pdf
Basic
2
Veuillez noter que les considérations GIL ne s'appliquent pas à tous les interprètes. Pour autant que je sache, IronPython et Jython fonctionnent sans GIL, ce qui permet à leur code d'utiliser plus efficacement le matériel multiprocesseur. Comme Arafangion l'a mentionné, l'interpréteur CPython peut également s'exécuter correctement en multi-thread si le code qui n'a pas besoin d'accéder aux éléments de données Python libère le verrou, puis l'acquiert à nouveau avant de revenir.
holdenweb
Qu'est-ce qui cause un changement de contexte entre les threads en Python? Est-ce basé sur des interruptions de minuterie? Blocage ou appel de rendement spécifique?
CMCDragonkai
36

Python est un langage assez facile à enfiler, mais il y a des mises en garde. La plus grande chose que vous devez savoir est le verrouillage global de l'interprète. Cela permet à un seul thread d'accéder à l'interpréteur. Cela signifie deux choses: 1) vous vous retrouvez rarement à utiliser une instruction de verrouillage en python et 2) si vous voulez profiter des systèmes multiprocesseurs, vous devez utiliser des processus séparés. EDIT: Je dois également souligner que vous pouvez mettre une partie du code en C / C ++ si vous souhaitez également contourner le GIL.

Ainsi, vous devez reconsidérer pourquoi vous souhaitez utiliser des threads. Si vous souhaitez paralléliser votre application pour tirer parti de l'architecture double cœur, vous devez envisager de diviser votre application en plusieurs processus.

Si vous souhaitez améliorer la réactivité, vous devez CONSIDÉRER l'utilisation de threads. Il existe cependant d'autres alternatives, à savoir le micro-lecture . Il existe également quelques cadres que vous devriez examiner:

Jason Baker
la source
@JS - Corrigé. Cette liste était de toute façon obsolète.
Jason Baker
Il me semble juste que vous ayez besoin de plusieurs processus - avec tous les frais généraux que cela implique - pour tirer parti d'un système multicœur. Nous avons des serveurs avec 32 cœurs logiques - j'ai donc besoin de 32 processus pour les utiliser efficacement? Madness
Basic
@Basic - La surcharge de démarrage d'un processus par rapport au démarrage d'un thread de nos jours est minime. Je suppose que vous pourriez commencer à voir des problèmes si nous parlons de milliers de requêtes par seconde, mais je remettrais en premier lieu le choix de Python pour un service aussi chargé.
Jason Baker
20

Voici un exemple de filetage de base. Il engendrera 20 threads; chaque thread affichera son numéro de thread. Exécutez-le et observez l'ordre dans lequel ils s'impriment.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Comme vous l'avez indiqué, les threads Python sont implémentés par découpage temporel. C'est ainsi qu'ils obtiennent l'effet «parallèle».

Dans mon exemple, ma classe Foo étend le thread, j'implémente ensuite la runméthode, qui est l'endroit où va le code que vous souhaitez exécuter dans un thread. Pour démarrer le thread que vous appelez start()sur l'objet thread, qui appellera automatiquement la runméthode ...

Bien sûr, ce ne sont que les bases. Vous voudrez éventuellement en savoir plus sur les sémaphores, les mutex et les verrous pour la synchronisation des threads et le passage de messages.

mmattax
la source
10

Utilisez des threads en python si les nœuds de calcul individuels effectuent des opérations liées aux E / S. Si vous essayez de mettre à l'échelle plusieurs cœurs sur une machine, recherchez un bon framework IPC pour python ou choisissez un autre langage.

Ben McNiel
la source
4

Remarque: partout où je mentionne, threadje veux dire spécifiquement les threads en python jusqu'à ce que cela soit explicitement indiqué.

Les threads fonctionnent un peu différemment en python si vous venez de l' C/C++arrière-plan. En python, un seul thread peut être en état d'exécution à un moment donné, ce qui signifie que les threads en python ne peuvent pas vraiment tirer parti de la puissance de plusieurs cœurs de traitement car, de par leur conception, il n'est pas possible pour les threads de s'exécuter en parallèle sur plusieurs cœurs.

Comme la gestion de la mémoire en python n'est pas sécurisée pour les threads, chaque thread nécessite un accès exclusif aux structures de données dans l'interpréteur python. Cet accès exclusif est acquis par un mécanisme appelé (verrou d'interprétation global) .GIL

Why does python use GIL?

Afin d'empêcher plusieurs threads d'accéder simultanément à l'état de l'interpréteur et de corrompre l'état de l'interpréteur.

L'idée est qu'à chaque fois qu'un thread est en cours d'exécution (même s'il s'agit du thread principal) , un GIL est acquis et après un certain intervalle de temps prédéfini, le GIL est libéré par le thread actuel et réacquis par un autre thread (le cas échéant).

Why not simply remove GIL?

Ce n'est pas qu'il soit impossible de supprimer GIL, c'est juste qu'au cours de cette opération, nous finissons par mettre plusieurs verrous à l'intérieur de l'interpréteur afin de sérialiser l'accès, ce qui rend même une seule application threadée moins performante.

Ainsi, le coût de la suppression de GIL est payé par les performances réduites d'une seule application threadée, ce qui n'est jamais souhaité.

So when does thread switching occurs in python?

Le changement de thread se produit lorsque GIL est libéré, alors quand GIL est-il publié? Il y a deux scénarios à prendre en considération.

Si un thread effectue des opérations liées au processeur (traitement d'image Ex).

Dans les anciennes versions de python, le changement de thread se produisait après un nombre fixe d'instructions python.Il était par défaut défini sur 100. peut très sauvagement de la milliseconde à même une seconde. Par conséquent, la libération de GIL après chaque 100instruction, quel que soit le temps qu'elles prennent pour s'exécuter, est une mauvaise politique.

Dans les nouvelles versions, au lieu d'utiliser le nombre d'instructions comme métrique pour changer de thread, un intervalle de temps configurable est utilisé. L'intervalle de commutation par défaut est de 5 millisecondes. Vous pouvez obtenir l'intervalle de commutation actuel à l'aide de sys.getswitchinterval(). Cela peut être modifié en utilisantsys.setswitchinterval()

Si un thread effectue des opérations liées aux
E / S (accès au système de fichiers Ex ou E / S réseau)

GIL est libéré chaque fois que le thread attend que certaines opérations d'E / S soient terminées.

Which thread to switch to next?

L'interpréteur n'a pas son propre ordonnanceur, quel thread est planifié à la fin de l'intervalle est la décision du système d'exploitation. .

anekix
la source
3

Une solution simple au GIL est le module multitraitement . Il peut être utilisé en remplacement du module de threading mais utilise plusieurs processus Interpreter au lieu de threads. Pour cette raison, il y a un peu plus de frais généraux que le threading ordinaire pour des choses simples, mais cela vous donne l'avantage d'une réelle parallélisation si vous en avez besoin. Il s'adapte également facilement à plusieurs machines physiques.

Si vous avez besoin d'une parallélisation à grande échelle, je chercherais plus loin, mais si vous voulez simplement mettre à l'échelle tous les cœurs d'un ordinateur ou quelques différents sans tout le travail nécessaire à la mise en œuvre d'un cadre plus complet, c'est pour vous. .

Willt
la source
2

Essayez de vous rappeler que le GIL est configuré pour interroger de temps en temps afin de montrer l'apparence de plusieurs tâches. Ce paramètre peut être affiné, mais je suggère qu'il devrait y avoir du travail que les threads font ou que de nombreux changements de contexte vont causer des problèmes.

J'irais jusqu'à suggérer plusieurs parents sur les processeurs et essayer de garder les mêmes emplois sur le (s) même (s) cœur (s).

phreaki
la source