Recherche…


Remarques

Pourquoi y a-t-il un GIL?

Le GIL existe dans CPython depuis la création des threads Python, en 1992. Il est conçu pour garantir la sécurité des threads lors de l’exécution du code python. Les interpréteurs Python écrits avec un GIL empêchent plusieurs threads natifs d'exécuter les bytecodes Python à la fois. Cela permet aux plug-ins de s'assurer que leur code est thread-safe: verrouillez simplement le GIL et seul votre thread actif peut s'exécuter, votre code est donc automatiquement compatible avec les threads.

Version courte: le GIL garantit que peu importe le nombre de processeurs et de threads que vous avez, un seul thread d'un interpréteur Python s'exécutera en même temps.

Cela présente de nombreux avantages en termes de facilité d'utilisation, mais présente également de nombreux avantages négatifs.

Notez qu'un GIL n'est pas une exigence du langage Python. Par conséquent, vous ne pouvez pas accéder au GIL directement à partir du code python standard. Toutes les implémentations de Python n'utilisent pas de GIL.

Interprètes ayant un GIL: CPython, PyPy, Cython (mais vous pouvez désactiver le GIL avec nogil )

Interprètes sans GIL: Jython, IronPython

Détails sur le fonctionnement du GIL:

Lorsqu'un thread est en cours d'exécution, il verrouille le GIL. Lorsqu'un thread veut s'exécuter, il demande le GIL et attend qu'il soit disponible. Dans CPython, avant la version 3.2, le thread en cours d'exécution vérifiait après un certain nombre d'instructions python pour voir si un autre code voulait le verrou (c'est-à-dire qu'il libérait le verrou et le demandait à nouveau). Cette méthode avait tendance à provoquer la famine des threads, en grande partie parce que le thread qui libérait le verrou l'acquitterait à nouveau avant que les threads en attente aient une chance de se réveiller. Depuis 3.2, les threads qui veulent le GIL attendent le verrouillage pendant un certain temps et, après ce temps, ils définissent une variable partagée qui force le thread en cours d'exécution à céder. Cela peut néanmoins entraîner des délais d'exécution considérablement plus longs. Voir les liens ci-dessous à partir de dabeaz.com (dans la section des références) pour plus de détails.

CPython libère automatiquement le GIL lorsqu'un thread effectue une opération d'E / S. Les bibliothèques de traitement d'images et les opérations de calcul numérique numérotées libèrent le GIL avant d'effectuer leur traitement.

Avantages du GIL

Pour les interprètes qui utilisent le GIL, le GIL est systémique. Il est utilisé pour préserver l'état de l'application. Les avantages comprennent:
  • Collecte des ordures - Le nombre de références thread-safe doit être modifié lorsque le GIL est verrouillé. Dans CPython, toute la collection Garbarge est liée au GIL. C'est un gros voir l'article wiki python.org sur le GIL (listé dans Références ci-dessous) pour plus de détails sur ce qui doit encore être fonctionnel si l'on voulait supprimer le GIL.
  • Facilité pour les programmeurs traitant du GIL - le verrouillage de tout est simpliste, mais facile à coder
  • Facilite l'importation de modules d'autres langages

Conséquences du GIL

Le GIL n'autorise qu'un seul thread à exécuter du code python à la fois dans l'interpréteur python. Cela signifie que le multithreading de processus exécutant un code Python strict ne fonctionne tout simplement pas. Lorsque vous utilisez des threads sur le GIL, vous aurez probablement de moins bonnes performances avec les threads que si vous les exécutiez dans un seul thread.

Les références:

https://wiki.python.org/moin/GlobalInterpreterLock - résumé rapide de ce qu'il fait, des détails précis sur tous les avantages

http://programmers.stackexchange.com/questions/186889/why-was-python-written-with-the-gil - résumé clairement écrit

http://www.dabeaz.com/python/UnderstandingGIL.pdf - comment fonctionne le GIL et pourquoi il ralentit sur plusieurs cœurs

http://www.dabeaz.com/GIL/gilvis/index.html - visualisation des données montrant comment le GIL verrouille les threads

http://jeffknupp.com/blog/2012/03/31/pythons-hardest-problem/ - simple à comprendre l'histoire du problème GIL

https://jeffknupp.com/blog/2013/06/30/pythons-hardest-problem-revisited/ - des détails sur la manière de contourner les limitations du GIL

Multiprocessing.Pool

La réponse simple à la question de savoir comment utiliser les threads dans Python est la suivante: "Ne le faites pas. Utilisez plutôt des processus." Le module de multitraitement vous permet de créer des processus avec une syntaxe similaire à la création de threads, mais je préfère utiliser leur objet Pool pratique.

En utilisant le code que David Beazley a d’abord utilisé pour montrer les dangers des threads sur le GIL , nous allons le réécrire à l’aide du multitraitement .

Le code de David Beazley qui a montré des problèmes de thread GIL

from threading import Thread
import time
def countdown(n):
    while n > 0:
        n -= 1

COUNT = 10000000

t1 = Thread(target=countdown,args=(COUNT/2,))
t2 = Thread(target=countdown,args=(COUNT/2,))
start = time.time()
t1.start();t2.start()
t1.join();t2.join()
end = time.time()
print end-start
Re-écrit en utilisant multiprocessing.Pool:
import multiprocessing
import time
def countdown(n):
    while n > 0:
        n -= 1

COUNT = 10000000

start = time.time()
with multiprocessing.Pool as pool:
    pool.map(countdown, [COUNT/2, COUNT/2])

    pool.close()
    pool.join()

end = time.time()
print(end-start)

Au lieu de créer des threads, cela crée de nouveaux processus. Comme chaque processus est son propre interpréteur, il n'y a pas de collision de GIL. multiprocessing.Pool ouvrira autant de processus qu'il y a de cœurs sur la machine, bien que dans l'exemple ci-dessus, il n'en faudrait que deux. Dans un scénario réel, vous souhaitez concevoir votre liste de manière à avoir au moins autant de longueur que des processeurs sur votre machine. Le pool exécutera la fonction que vous lui indiquez pour chaque argument, jusqu'au nombre de processus qu'il crée. Lorsque la fonction se termine, toutes les fonctions restantes dans la liste seront exécutées sur ce processus.

J'ai trouvé que, même en utilisant l'instruction with , si vous ne fermez pas et ne rejoignez pas le pool, les processus continuent d'exister. Pour nettoyer les ressources, je ferme et joignais toujours mes pools.

Cython nogil:

Cython est un interpréteur Python alternatif. Il utilise le GIL, mais vous permet de le désactiver. Voir leur documentation

À titre d'exemple, en utilisant le code que David Beazley a utilisé pour montrer les dangers des threads contre le GIL , nous allons le réécrire en utilisant nogil:

Le code de David Beazley qui a montré des problèmes de thread GIL

from threading import Thread
import time
def countdown(n):
    while n > 0:
        n -= 1

COUNT = 10000000

t1 = Thread(target=countdown,args=(COUNT/2,))
t2 = Thread(target=countdown,args=(COUNT/2,))
start = time.time()
t1.start();t2.start()
t1.join();t2.join()
end = time.time()
print end-start

Réécrit en utilisant nogil (SEULEMENT FONCTIONNE À CYTHON):

from threading import Thread
import time
def countdown(n):
    while n > 0:
        n -= 1

COUNT = 10000000

with nogil:
    t1 = Thread(target=countdown,args=(COUNT/2,))
    t2 = Thread(target=countdown,args=(COUNT/2,))
    start = time.time()
    t1.start();t2.start()
    t1.join();t2.join()
    
end = time.time()
print end-start

C'est si simple, tant que vous utilisez cython. Notez que la documentation indique que vous devez vous assurer de ne modifier aucun objet python:

Le code dans le corps de l'instruction ne doit en aucun cas manipuler les objets Python, et ne doit rien appeler qui manipule les objets Python sans avoir préalablement racheté le GIL. Cython ne vérifie pas actuellement cela.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow