web-dev-qa-db-fra.com

Qu'est-ce que le verrou d'interprète global (GIL) dans CPython?

Qu'est-ce qu'un verrou d'interprète global et pourquoi est-ce un problème?

Beaucoup de bruit a été fait autour de la suppression du GIL de Python, et j'aimerais comprendre pourquoi c'est si important. Je n'ai jamais écrit un compilateur ni un interprète, alors ne soyez pas frugal avec les détails, j'aurai probablement besoin d'eux pour comprendre.

221
e-satis

GIL de Python est destiné à sérialiser l'accès aux ressources internes de l'interprète à partir de différents threads. Sur les systèmes multicœurs, cela signifie que plusieurs threads ne peuvent pas utiliser efficacement plusieurs cœurs. (Si le GIL n’a pas conduit à ce problème, la plupart des gens ne s’intéresseraient pas au GIL - c’est un problème qui tient uniquement à la prévalence croissante des systèmes multicœurs.) Si vous voulez comprendre cela en détail, vous pouvez voir cette vidéo ou regarder cet ensemble de diapositives . C'est peut-être trop d'informations, mais alors vous avez demandé des détails :-)

Notez que GIL de Python n’est vraiment qu’un problème pour CPython, l’implémentation de référence. Jython et IronPython n'ont pas de GIL. En tant que développeur Python, vous ne rencontrez généralement pas le GIL sauf si vous écrivez une extension C. Les auteurs d’extensions C doivent libérer le GIL lorsque leurs extensions bloquent les E/S, donc que les autres threads du processus Python aient une chance de s'exécuter.

204
Vinay Sajip

Supposons que vous ayez plusieurs threads qui ne touchent pas vraiment les données de chacun. Ceux-ci devraient exécuter aussi indépendamment que possible. Si vous avez un "verrou global" que vous devez acquérir pour (par exemple) appeler une fonction, cela peut se transformer en goulot d'étranglement. En fin de compte, vous pouvez ne pas tirer grand bénéfice de plusieurs threads.

Pour le mettre dans une analogie réelle: imaginez 100 développeurs travaillant dans une entreprise ne disposant que d’une seule tasse de café. La plupart des développeurs passaient leur temps à attendre du café au lieu de coder.

Rien de tout cela n'est spécifique à Python - je ne connais pas les détails de ce pour quoi Python avait besoin d'un GIL pour la première fois. Cependant, j'espère qu'il vous a donné une meilleure idée du concept général.

55
Jon Skeet

Commençons par comprendre ce que le python GIL fournit:

Toute opération/instruction est exécutée dans l'interpréteur. GIL garantit que l'interprète est tenu par un seul thread à à un instant donné . Et votre programme python avec plusieurs threads fonctionne dans un seul interpréteur. À un moment donné, cet interpréteur est tenu par un seul thread. Cela signifie que seul le thread qui détient l'interprète est en cours d'exécution à à tout instant .

Maintenant, pourquoi est-ce un problème:

Votre machine peut avoir plusieurs cœurs/processeurs. Et plusieurs cœurs permettent à plusieurs threads de s’exécuter simultanément , c’est-à-dire que plusieurs threads peuvent s’exécuter à un moment donné. . Mais puisque l'interprète est tenu par un seul thread, les autres threads ne font rien même s'ils ont accès à un noyau. Ainsi, vous ne bénéficiez d'aucun avantage fourni par plusieurs cœurs car, à tout moment, un seul cœur, qui est le cœur utilisé par le thread qui détient actuellement l'interpréteur, est utilisé. Ainsi, votre programme prendra autant de temps à s’exécuter que s’il s’agissait d’un programme à thread unique.

Toutefois, les opérations potentiellement bloquantes ou de longue durée, telles que les E/S, le traitement des images et la réduction du nombre de numéros NumPy, ont lieu en dehors de la liste GIL. Tiré de ici . Donc, pour de telles opérations, une opération multithread sera toujours plus rapide qu'une seule opération en mode thread malgré la présence de GIL. Donc, GIL n'est pas toujours un goulot d'étranglement.

Edit: GIL est un détail de la mise en oeuvre de CPython. IronPython et Jython n'ayant pas GIL, un programme véritablement multithread devrait y être possible, pensant que je n'avais jamais utilisé PyPy et Jython et que je n'en étais pas sûr.

31
Akshar Raaj

Python n'autorise pas le multi-threading dans le vrai sens du mot. Il contient un package multi-threading, mais si vous souhaitez utiliser plusieurs threads pour accélérer votre code, il est généralement déconseillé de l'utiliser. Python a une construction appelée Global Interpreter Lock (GIL).

https://www.youtube.com/watch?v=ph374fJqFPE

Le GIL veille à ce qu'un seul de vos "threads" puisse être exécuté à la fois. Un thread acquiert le GIL, fait un peu de travail, puis passe le GIL sur le prochain thread. Cela se produit très rapidement, alors, pour l’œil humain, il peut sembler que vos threads s’exécutent en parallèle, mais ils ne font que jouer à tour de rôle en utilisant le même cœur de processeur. Tout ce passage de GIL ajoute une surcharge à l'exécution. Cela signifie que si vous voulez que votre code soit exécuté plus rapidement, l'utilisation du package de threading n'est souvent pas une bonne idée.

Il y a des raisons d'utiliser le package de threading de Python. Si vous souhaitez gérer simultanément certaines tâches et que l'efficacité n'est pas un problème, c'est tout à fait correct et pratique. Ou si vous exécutez du code qui doit attendre quelque chose (comme un IO), alors cela peut avoir beaucoup de sens. Mais la bibliothèque de threads ne vous laissera pas utiliser de cœurs de processeur supplémentaires.

Le multi-threading peut être externalisé vers le système d'exploitation (en faisant du multi-traitement), une application externe qui appelle votre code Python (par exemple, Spark ou Hadoop ), ou du code que votre Python appelle du code (par exemple: vous pourriez avoir votre Python appelez une fonction C qui effectue le coûteux processus multithread)) .

16
Ijaz Ahmad Khan

Chaque fois que deux threads ont accès à la même variable, vous avez un problème. En C++, par exemple, le moyen d'éviter le problème consiste à définir un verrou mutex afin d'empêcher deux threads d'entrer, par exemple, le setter d'un objet en même temps.

Le multithreading est possible en python, mais deux threads ne peuvent pas être exécutés en même temps avec une granularité plus fine qu'une instruction python). Le thread en cours d'exécution obtient un verrou global appelé GIL.

Cela signifie que si vous commencez à écrire du code multithread afin de tirer parti de votre processeur multicœur, vos performances ne s'amélioreront pas. La solution habituelle consiste à utiliser plusieurs processus.

Notez qu'il est possible de publier le GIL si vous utilisez une méthode que vous avez écrite en C, par exemple.

L'utilisation d'un GIL n'est pas inhérente à Python mais à certains de ses interpréteurs, y compris au CPython le plus courant. (#Edited, see comment)

Le numéro GIL est toujours valide dans Python 3000.

14
fulmicoton

Documentation Python 3.7

Je voudrais également souligner la citation suivante de Python threading documentation :

Détail de l’implémentation de CPython: Dans CPython, en raison du verrou d’interprète global, un seul thread peut exécuter le code Python à la fois (même si certaines bibliothèques orientées sur les performances risquent de dépasser cette limitation). Afin de mieux utiliser les ressources de calcul des machines multicœurs, il est conseillé d’utiliser multiprocessing ou concurrent.futures.ProcessPoolExecutor. Toutefois, le threading reste un modèle approprié si vous souhaitez exécuter plusieurs tâches liées aux E/S simultanément.

Ceci est un lien vers l'entrée entrée du glossaire pour global interpreter lock qui explique que la GIL implique que le parallélisme fileté dans Python ne convient pas pour tâches liées au processeur :

Le mécanisme utilisé par l'interpréteur CPython pour s'assurer qu'un seul thread exécute Python à la fois. Cela simplifie l'implémentation de CPython en rendant le modèle objet (y compris les types intégrés critiques tels que dict) implicitement protégé contre les accès simultanés. Le verrouillage de l’interprète dans son intégralité lui permet d’être plus multi-threadé, au détriment d’une grande partie du parallélisme offert par les machines multiprocesseurs.

Toutefois, certains modules d’extension, standard ou tiers, sont conçus de manière à libérer le fichier GIL lorsqu’il effectue des tâches de calcul intensif telles que la compression ou le hachage. De plus, le GIL est toujours publié lors de la réalisation des E/S.

Les efforts déployés dans le passé pour créer un interpréteur "à thread libre" (qui verrouille les données partagées avec une granularité beaucoup plus fine) n'ont pas abouti, car les performances ont souffert dans le cas commun du processeur unique. On pense que résoudre ce problème de performances rendrait la mise en œuvre beaucoup plus compliquée et donc plus coûteuse à maintenir.

Cette citation implique également que les dict et donc les affectations de variables sont également thread-safe en tant que détail d'implémentation CPython

Ensuite, docs pour le paquetage multiprocessing explique comment il surmonte la GIL en créant une interface similaire à celle de threading:

le multitraitement est un package qui prend en charge les processus de génération utilisant une API similaire au module de threading. Le package de multitraitement offre une concurrence simultanée locale et distante, contournant efficacement le verrou d'interprète global en utilisant des sous-processus au lieu de threads. De ce fait, le module de multitraitement permet au programmeur de tirer pleinement parti de plusieurs processeurs sur une machine donnée. Il fonctionne sous Unix et Windows.

Et le docs for concurrent.futures.ProcessPoolExecutor explique qu'il utilise multiprocessing comme serveur principal:

La classe ProcessPoolExecutor est une sous-classe Executor qui utilise un pool de processus pour exécuter des appels de manière asynchrone. ProcessPoolExecutor utilise le module de multitraitement, ce qui lui permet de contourner le verrou d'interprète global, mais signifie également que seuls les objets pouvant être sélectionnés peuvent être exécutés et renvoyés.

ce qui devrait être mis en contraste avec l'autre classe de base ThreadPoolExecutor que tilise des threads au lieu de processus

ThreadPoolExecutor est une sous-classe d'Executor qui utilise un pool de threads pour exécuter des appels de manière asynchrone.

nous en concluons que ThreadPoolExecutor ne convient que pour les tâches liées aux E/S, tandis que ProcessPoolExecutor peut également gérer des tâches liées à la CPU.

La question suivante demande pourquoi le GIL existe en premier lieu: Pourquoi le verrou d'interprète global?

Processus vs expériences de threads

At Multiprocessing vs Threading Python J'ai effectué une analyse expérimentale du processus vs des threads en Python.

Aperçu rapide des résultats:

enter image description here

Je souhaite partager un exemple tiré du livre multithreading pour Visual Effects. Alors, voici une situation classique de blocage total

static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...    
}

Considérons maintenant les événements de la séquence résultant d'un blocage total.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
║   ║ Main Thread                            ║ Other Thread                         ║
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
║ 1 ║ Python Command acquires GIL            ║ Work started                         ║
║ 2 ║ Computation requested                  ║ MyCallback runs and acquires MyMutex ║
║ 3 ║                                        ║ MyCallback now waits for GIL         ║
║ 4 ║ MyCallback runs and waits for MyMutex  ║ waiting for GIL                      ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
0
user1767754

Pourquoi Python (CPython et autres) utilise le GIL

De http://wiki.python.org/moin/GlobalInterpreterLock

Dans CPython, le verrou d'interprète global, ou GIL, est un mutex qui empêche l'exécution simultanée de plusieurs threads natifs Python Python. Ce verrou est nécessaire principalement parce que la gestion de la mémoire de CPython n'est pas thread-safe.

Comment le supprimer de Python?

Comme Lua, peut-être Python pourrait démarrer plusieurs ordinateurs virtuels, mais python ne le fait pas, je suppose qu'il devrait y avoir d'autres raisons.

Dans Numpy ou dans une autre python bibliothèque étendue, parfois, la publication de GIL dans d’autres threads pourrait accroître l’efficacité de l’ensemble du programme.

0
maoyang