Python prend-il en charge le multithreading? Peut-il accélérer le temps d'exécution?

95

Je ne sais pas si le multithreading fonctionne ou non en Python.

Je sais qu'il y a eu beaucoup de questions à ce sujet et j'en ai lu beaucoup, mais je suis toujours confus. Je sais d'après ma propre expérience et j'ai vu d'autres personnes publier leurs propres réponses et exemples ici sur StackOverflow que le multithreading est en effet possible en Python. Alors pourquoi tout le monde continue-t-il à dire que Python est verrouillé par le GIL et qu'un seul thread peut s'exécuter à la fois? Cela fonctionne clairement. Ou y a-t-il une distinction que je ne parviens pas ici?

De nombreuses affiches / répondants continuent également de mentionner que le filetage est limité car il n'utilise pas plusieurs cœurs. Mais je dirais qu'ils sont toujours utiles car ils fonctionnent simultanément et accélèrent ainsi la charge de travail combinée. Je veux dire, pourquoi y aurait-il même un module de thread Python autrement?

Mettre à jour:

Merci pour toutes les réponses à ce jour. La façon dont je comprends les choses est que le multithreading ne fonctionnera qu'en parallèle pour certaines tâches d'E / S, mais ne peut en exécuter qu'une à la fois pour les tâches de base multiples liées au processeur.

Je ne suis pas tout à fait sûr de ce que cela signifie pour moi en termes pratiques, je vais donc simplement donner un exemple du type de tâche que j'aimerais effectuer en multithread. Par exemple, disons que je souhaite parcourir une très longue liste de chaînes et que je souhaite effectuer des opérations de base sur les chaînes sur chaque élément de la liste. Si je divise la liste, envoie chaque sous-liste à traiter par mon code de boucle / chaîne dans un nouveau thread, et renvoie les résultats dans une file d'attente, ces charges de travail s'exécuteront-elles à peu près en même temps? Plus important encore, cela accélérera-t-il théoriquement le temps nécessaire à l'exécution du script?

Un autre exemple pourrait être si je peux rendre et enregistrer quatre images différentes en utilisant PIL dans quatre threads différents, et est-ce que cela est plus rapide que de traiter les images une par une après l'autre? Je suppose que ce composant de vitesse est ce que je me demande vraiment plutôt que quelle est la terminologie correcte.

Je connais également le module de multitraitement, mais mon intérêt principal pour le moment est pour les charges de tâches petites à moyennes (10-30 secondes) et je pense donc que le multithreading sera plus approprié car les sous-processus peuvent être lents à démarrer.

Karim Bahgat
la source
4
C'est une question assez chargée. Je pense que la réponse réside dans ce que vous voulez que les threads fassent. Dans la plupart des cas, le GIL empêche plus d'un thread de s'exécuter simultanément. Cependant, il existe quelques cas où le GIL est libéré (par exemple la lecture à partir d'un fichier) afin que cela puisse être fait en parallèle. Notez également que le GIL est un détail d'implémentation de Cpython (l'implémentation la plus courante). Aucune autre implémentation de python (Jython, PyPy, etc.) n'a de GIL (AFAIK)
mgilson
2
@mgilson PyPy a un GIL.
2
@delnan - Vous semblez avoir raison. Merci.
mgilson
1
«les sous-processus peuvent être lents à démarrer» - vous pouvez créer un pool de tâches prêtes à être exécutées. La surcharge peut être limitée à peu près au temps nécessaire pour sérialiser / désérialiser les données requises pour que la tâche commence à fonctionner.
Brian Cain
1
@KarimBahgat, c'est exactement ce que je veux dire.
Brian Cain

Réponses:

132

Le GIL n'empêche pas le filetage. Tout ce que fait le GIL est de s'assurer qu'un seul thread exécute le code Python à la fois; le contrôle bascule toujours entre les threads.

Ce que le GIL empêche alors, c'est d'utiliser plus d'un cœur de processeur ou des processeurs séparés pour exécuter des threads en parallèle.

Cela ne s'applique qu'au code Python. Les extensions C peuvent publier et libèrent le GIL pour permettre à plusieurs threads de code C et à un thread Python de s'exécuter sur plusieurs cœurs. Cela s'étend aux E / S contrôlées par le noyau, telles que les select()appels de lecture et d'écriture de socket, ce qui permet à Python de gérer les événements réseau de manière raisonnablement efficace dans une configuration multicœur multi-thread.

Ce que font de nombreux déploiements de serveurs, c'est d'exécuter plus d'un processus Python, pour laisser le système d'exploitation gérer la planification entre les processus afin d'utiliser vos cœurs de processeur au maximum. Vous pouvez également utiliser la multiprocessingbibliothèque pour gérer le traitement parallèle sur plusieurs processus à partir d'une base de code et d'un processus parent, si cela convient à vos cas d'utilisation.

Notez que le GIL n'est applicable qu'à l'implémentation CPython; Jython et IronPython utilisent une implémentation de thread différente (respectivement les threads d'exécution communs Java VM et .NET natifs).

Pour adresser directement votre mise à jour: toute tâche qui tente d'obtenir une augmentation de vitesse à partir d'une exécution parallèle, en utilisant du code Python pur, ne verra pas d'accélération car le code Python threadé est verrouillé sur un thread s'exécutant à la fois. Cependant, si vous mélangez des extensions C et des E / S (comme des opérations PIL ou numpy) et n'importe quel code C peut s'exécuter en parallèle avec un thread Python actif.

Le thread Python est idéal pour créer une interface graphique réactive ou pour gérer plusieurs requêtes Web courtes où les E / S sont le goulot d'étranglement plus que le code Python. Il ne convient pas pour paralléliser du code Python intensif en calcul, s'en tenir au multiprocessingmodule pour de telles tâches ou déléguer à une bibliothèque externe dédiée.

Martijn Pieters
la source
Merci @MartijnPieters, alors j'ai une réponse plus claire à ma question de savoir si le threading peut être utilisé pour accélérer le code tel qu'une boucle for, qui est "non". Peut-être que vous ou quelqu'un pourriez écrire une nouvelle réponse que je peux accepter qui fournit des exemples spécifiques de modules / codes / opérations courants où le threading sera autorisé par le GIL à fonctionner parallèlement et donc plus rapidement (par exemple, des exemples de ces E / S et réseau / les opérations de lecture de socket qui ont été mentionnées, et tous les autres cas où le multithreading en Python est utile). Peut-être une belle liste d'utilisations multithread courantes et quelques exemples de programmation si possible?
Karim Bahgat le
4
Non, je ne pense pas qu'une telle réponse serait très utile; pour être honnête. Vous ne pouvez jamais créer une liste exhaustive, mais la règle de base est que toutes les E / S (lecture et écriture de fichiers, sockets réseau, canaux) sont gérées en C, et de nombreuses bibliothèques C publient également le GIL pour leur opérations, mais il appartient aux bibliothèques de documenter cela pour vous.
Martijn Pieters
1
Mon mauvais, je n'ai pas vu votre réponse mise à jour jusqu'à présent, où vous avez donné de bons exemples d'utilisation des threads. Ceux-ci incluaient (corrigez-moi si je me trompe) la programmation réseau (par exemple urllib.urlopen()?), Pour appeler un script Python à partir d'une interface graphique Python, et appeler plusieurs opérations PIL (par exemple Image.transform()) et numpy (par exemple numpy.array()) avec des threads. Et vous avez fourni d'autres exemples dans votre commentaire, comme l'utilisation de plusieurs threads pour lire des fichiers (par exemple f.read()?). Je sais qu'une liste exhaustive n'est pas possible, je voulais juste les types d'exemples que vous avez donnés dans votre mise à jour. Quoi qu'il en soit, j'ai accepté votre réponse :)
Karim Bahgat
2
@KarimBahgat: Oui, urllib.urlopen()invoquerait les sockets réseau, attendre les E / S de socket est une excellente opportunité de changer de thread et de faire autre chose.
Martijn Pieters
4
Bien que ce ne soit pas directement pertinent pour ce problème, il convient de noter que parfois le threading n'est pas du tout une question de performances; il peut être simplement plus simple d'écrire votre code sous forme de plusieurs threads d'exécution indépendants. Par exemple, vous pouvez avoir un thread jouant de la musique de fond, un entretien de l'interface utilisateur et un autre pour les calculs qui doivent être effectués éventuellement mais qui ne sont pas pressés. Essayer de séquencer la lecture du prochain tampon audio avec la boucle d'exécution de l'interface utilisateur, ou de décomposer votre calcul en morceaux suffisamment petits pour ne pas interférer avec l'interactivité, peut être beaucoup plus difficile que d'utiliser des threads.
abarnert
4

Oui. :)

Vous avez le module de filetage de bas niveau et le module de filetage de niveau supérieur . Mais si vous souhaitez simplement utiliser des machines multicœurs, le module multitraitement est la solution.

Citation de la documentation :

Dans CPython, en raison du verrouillage global de l'interpréteur, un seul thread peut exécuter du code Python à la fois (même si certaines bibliothèques axées sur les performances peuvent surmonter cette limitation). Si vous souhaitez que votre application utilise au mieux les ressources de calcul des machines multicœurs, il est conseillé d'utiliser le multitraitement. Cependant, le threading est toujours un modèle approprié si vous souhaitez exécuter simultanément plusieurs tâches liées aux E / S.

zord
la source
3

Le thread est autorisé en Python, le seul problème est que le GIL s'assurera qu'un seul thread est exécuté à la fois (pas de parallélisme).

Donc, fondamentalement, si vous voulez multi-threader le code pour accélérer le calcul, cela ne l'accélérera pas car un seul thread est exécuté à la fois, mais si vous l'utilisez pour interagir avec une base de données par exemple, ce sera le cas.

r.guerbab
la source