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


244

Qu'est-ce qu'un verrou d'interpréteur 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 de compilateur ni d'interprète moi-même, alors ne soyez pas frugal avec les détails, j'en aurai probablement besoin pour comprendre.


3
Regardez David Beazley vous dire tout ce que vous avez toujours voulu savoir sur le GIL.
hughdbrown

1
Voici un article assez long sur le GIL et le filetage en Python que j'ai écrit il y a un moment. Il rentre dans pas mal
jnoller

Voici un code démontrant les effets de GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu

3
Je trouve que c'est la meilleure explication de GIL. Lisez s'il vous plaît. dabeaz.com/python/UnderstandingGIL.pdf
suhao399

realpython.com/python-gil J'ai trouvé cela utile
qwr

Réponses:


220

Le GIL de Python est destiné à sérialiser l'accès aux interpréteurs internes 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 se soucieraient pas du GIL - il est uniquement soulevé comme un problème en raison de la prévalence croissante des systèmes multicœurs.) Si vous voulez le comprendre en détail, vous pouvez visionner cette vidéo ou regarder cet ensemble de diapositives . C'est peut-être trop d'informations, mais vous avez demandé des détails :-)

Notez que GIL de Python n'est vraiment un problème que 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 rédacteurs d'extensions C doivent libérer le GIL lorsque leurs extensions bloquent les E / S, afin que les autres threads du processus Python puissent s'exécuter.


46
Bonne réponse - en gros, cela signifie que les threads en Python ne sont bons que pour bloquer les E / S; votre application ne dépassera jamais 1 cœur CPU d'utilisation du processeur
Ana Betts

8
"En tant que développeur Python, vous ne rencontrez généralement pas le GIL à moins que vous n'écriviez une extension C" - Vous ne savez peut-être pas que la cause de votre code multi-thread s'exécutant à un rythme d'escargots est le GIL, mais vous ' Nous en ressentirons certainement les effets. Cela m'étonne toujours que pour profiter d'un serveur 32 cœurs avec Python, j'ai besoin de 32 processus avec tous les frais généraux associés.
Basic

6
@PaulBetts: ce n'est pas vrai. Il est probable que les performances du code critique utilise déjà des extensions C qui peuvent et libérer GIL par exemple regex, lxml, numpymodules. Cython permet de publier GIL en code personnalisé, par exemple,b2a_bin(data)
jfs

5
@Paul Betts: Vous pouvez obtenir plus de 1 code CPU d'utilisation du processeur en utilisant le module multiprocessing . La création de plusieurs processus est "plus lourde" que la création de plusieurs threads, mais si vous avez vraiment besoin de travailler en parallèle, en python, c'est une option.
AJNeufeld

1
@david_adler Oui, toujours le cas, et le restera probablement encore un certain temps. Cela n'a pas vraiment empêché Python d'être vraiment utile pour de nombreuses charges de travail différentes.
Vinay Sajip

59

Supposons que vous ayez plusieurs threads qui ne sont pas vraiment touchent les données les uns des autres. Ceux-ci devraient s'exécuter de manière aussi indépendante que possible. Si vous avez un "verrou global" que vous devez acquérir afin (par exemple) d'appeler une fonction, cela peut finir comme un goulot d'étranglement. Vous pouvez finir par ne pas tirer beaucoup d'avantages d'avoir plusieurs threads en premier lieu.

Pour le mettre dans une analogie réelle: imaginez 100 développeurs travaillant dans une entreprise avec une seule tasse de café. La plupart des développeurs passeraient leur temps à attendre le café au lieu de coder.

Rien de tout cela n'est spécifique à Python - je ne connais pas les détails de ce que Python avait besoin d'un GIL en premier lieu. Cependant, j'espère que cela vous a donné une meilleure idée du concept général.


Sauf qu'attendre la tasse de café semble être un processus assez lié aux E / S, car ils peuvent sûrement faire d'autres choses en attendant la tasse. Le GIL a très peu d'effet sur les threads lourds d'E / S qui passent la plupart de leur temps à attendre de toute façon.
Cruncher


36

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éteur est détenu par un seul thread à un instant donné . Et votre programme python avec plusieurs threads fonctionne dans un seul interpréteur. À tout instant particulier, cet interprète est détenu par un seul thread. Cela signifie que seul le thread qui contient l'interpréteur s'exécute à 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 pourraient s'exécuter à tout instant particulier.. Mais puisque l'interpréteur est détenu par un seul thread, les autres threads ne font rien même s'ils ont accès à un noyau. Ainsi, vous n'obtenez aucun avantage fourni par plusieurs cœurs, car à tout instant, un seul cœur, qui est le cœur utilisé par le thread contenant actuellement l'interpréteur, est utilisé. Ainsi, votre programme prendra autant de temps à exécuter que s'il s'agissait d'un programme à thread unique.

Cependant, des opérations potentiellement bloquantes ou de longue durée, telles que les E / S, le traitement d'image et la compression des nombres NumPy, se produisent en dehors du GIL. Pris d' ici . Ainsi, pour de telles opérations, une opération multithread sera toujours plus rapide qu'une opération à thread unique malgré la présence de GIL. Ainsi, GIL n'est pas toujours un goulot d'étranglement.

Edit: GIL est un détail d'implémentation de CPython. IronPython et Jython n'ont pas GIL, donc un programme vraiment multithread devrait être possible en eux, pensant que je n'ai jamais utilisé PyPy et Jython et que je n'en suis pas sûr.


4
Remarque : PyPy a le GIL . Référence : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . Ironpython et Jython n'ont pas le GIL.
Tasdik Rahman

En effet, PyPy a un GIL, mais pas IronPython.
Emmanuel

@Emmanuel Modifié la réponse pour supprimer PyPy et inclure IronPython.
Akshar Raaj

17

Python n'autorise pas le multi-threading dans le vrai sens du mot. Il a un package multi-thread mais si vous voulez multi-thread pour accélérer votre code, alors ce n'est généralement pas une bonne idée de l'utiliser. Python a une construction appelée Global Interpreter Lock (GIL).

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

Le GIL s'assure qu'un seul de vos «threads» peut s'exécuter à la fois. Un thread acquiert le GIL, fait un peu de travail, puis passe le GIL sur le thread suivant. Cela se produit très rapidement, donc à l'œil humain, il peut sembler que vos threads s'exécutent en parallèle, mais ils se contentent de tourner à tour de rôle avec le même cœur de processeur. Tout ce passage GIL ajoute des frais généraux à l'exécution. Cela signifie que si vous souhaitez accélérer l'exécution de votre code, l'utilisation du package de thread n'est souvent pas une bonne idée.

Il y a des raisons d'utiliser le package de threading de Python. Si vous voulez exécuter certaines choses simultanément 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 certains IO), cela pourrait avoir beaucoup de sens. Mais la bibliothèque de threads ne vous permettra pas d'utiliser des cœurs de processeur supplémentaires.

Le multi-threading peut être externalisé vers le système d'exploitation (en effectuant un multi-traitement), une application externe qui appelle votre code Python (par exemple, Spark ou Hadoop), ou du code que votre code Python appelle (par exemple: vous pourriez avoir votre Python appelez une fonction C qui fait les choses coûteuses multi-thread).


15

Chaque fois que deux threads ont accès à la même variable, vous avez un problème. En C ++ par exemple, la façon d'éviter le problème est de définir un verrou mutex pour empêcher deux threads d'entrer, disons, en même temps dans le setter d'un objet.

Le multithreading est possible en python, mais deux threads ne peuvent pas être exécutés en même temps à 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 profiter de votre processeur multicœur, vos performances ne s'amélioreront pas. La solution de contournement habituelle consiste à passer par plusieurs processus.

Notez qu'il est possible de libérer le GIL si vous êtes dans 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ètes, y compris le CPython le plus courant. (# édité, voir commentaire)

Le problème GIL est toujours valide dans Python 3000.


Stackless a toujours un GIL. Stackless n'améliore pas le threading (comme dans le module) - il offre une méthode de programmation différente (coroutines) qui tente de contourner le problème, mais nécessite des fonctions non bloquantes.
jnoller

Qu'en est-il du nouveau GIL en 3.2?
new123456

Juste pour ajouter que vous n'avez pas de problème / besoin de mutex / sémaphores si un seul thread mettra à jour la mémoire. @ new123456, il réduit les conflits et planifie mieux les threads sans nuire aux performances à un seul thread (ce qui est impressionnant en soi), mais c'est toujours un verrou global.
Basic

14

Documentation Python 3.7

Je voudrais également souligner la citation suivante de la documentation Pythonthreading :

Détail de l'implémentation de CPython: dans CPython, en raison du verrouillage de l'interpréteur global, 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 mieux les ressources de calcul des machines multicœurs, il est conseillé d'utiliser multiprocessingou concurrent.futures.ProcessPoolExecutor. Cependant, le thread est toujours un modèle approprié si vous souhaitez exécuter simultanément plusieurs tâches liées aux E / S.

Ce lien renvoie à l' entréeglobal interpreter lock du glossaire qui explique que le GIL implique que le parallélisme fileté en Python ne convient pas aux tâches liées au processeur :

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

Cependant, certains modules d'extension, standard ou tiers, sont conçus pour libérer le GIL lors de l'exécution de tâches à forte intensité de calcul telles que la compression ou le hachage. De plus, le GIL est toujours libéré lors des E / S.

Les efforts antérieurs pour créer un interpréteur «libre» (celui qui verrouille les données partagées à une granularité beaucoup plus fine) n'ont pas été couronnés de succès car les performances ont souffert dans le cas d'un seul processeur commun. On pense que surmonter ce problème de performance rendrait la mise en œuvre beaucoup plus compliquée et donc plus coûteuse à maintenir.

Cette citation implique également que les dicts et donc l'affectation des variables sont également thread-safe en tant que détail d'implémentation CPython:

Ensuite, les documents du multiprocessingpackage expliquent comment il surmonte le GIL en générant un processus tout en exposant une interface similaire à celle de threading:

le multiprocessing est un package qui prend en charge les processus de génération à l'aide d'une API similaire au module de thread. Le package de multitraitement offre une concurrence à la fois locale et distante, évitant efficacement le verrouillage d'interpréteur global en utilisant des sous-processus au lieu de threads. Pour cette raison, le module multiprocesseur permet au programmeur de tirer pleinement parti de plusieurs processeurs sur une machine donnée. Il fonctionne sur Unix et Windows.

Et les documentsconcurrent.futures.ProcessPoolExecutor expliquent qu'il utilise multiprocessingcomme backend:

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éteur global, mais signifie également que seuls les objets picklables peuvent être exécutés et renvoyés.

qui devrait être contrasté avec l'autre classe de base ThreadPoolExecutorqui utilise des threads au lieu de processus

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

d'où nous concluons qu'il ThreadPoolExecutorne convient qu'aux tâches liées aux E / S, tout en ProcessPoolExecutorpouvant également gérer les tâches liées au processeur.

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

Expériences processus vs thread

Chez Multiprocessing vs Threading Python, j'ai fait une analyse expérimentale des processus vs threads en Python.

Aperçu rapide des résultats:

entrez la description de l'image ici


0

Pourquoi Python (CPython et autres) utilise le GIL

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

Dans CPython, le verrou d'interpréteur global, ou GIL, est un mutex qui empêche plusieurs threads natifs d'exécuter des bytecodes Python à la fois. 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 que Python pourrait démarrer plusieurs VM, mais python ne fait pas cela, je suppose qu'il devrait y avoir d'autres raisons.

Dans Numpy ou une autre bibliothèque étendue python, parfois, la libération de GIL sur d'autres threads pourrait augmenter l'efficacité de l'ensemble du programme.


0

Je veux partager un exemple du livre multithreading pour Visual Effects. Voici donc une situation de blocage classique

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

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

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    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                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.