Zoeken…


Opmerkingen

Waarom is er een GIL?

De GIL bestaat al in CPython sinds het begin van Python-threads, in 1992. Het is ontworpen om de threadveiligheid van de lopende python-code te waarborgen. Python-interpreters die met een GIL zijn geschreven, voorkomen dat meerdere native threads Python-bytecodes tegelijk uitvoeren. Dit maakt het voor plug-ins gemakkelijk om ervoor te zorgen dat hun code thread-safe is: vergrendel gewoon de GIL en alleen uw actieve thread kan worden uitgevoerd, zodat uw code automatisch thread-safe is.

Korte versie: de GIL zorgt ervoor dat ongeacht hoeveel processors en threads u hebt, slechts één thread van een python-interpreter tegelijkertijd kan worden uitgevoerd.

Dit heeft veel gebruiksgemaksvoordelen, maar heeft ook veel negatieve voordelen.

Merk op dat een GIL geen vereiste is voor de Python-taal. Bijgevolg hebt u geen rechtstreeks toegang tot de GIL vanuit de standaard python-code. Niet alle implementaties van Python gebruiken een GIL.

Tolken met een GIL: CPython, PyPy, Cython (maar u kunt de GIL uitschakelen met nogil )

Tolken zonder GIL: Jython, IronPython

Details over hoe de GIL werkt:

Wanneer een thread loopt, vergrendelt deze de GIL. Wanneer een thread wil worden uitgevoerd, vraagt deze de GIL aan en wacht totdat deze beschikbaar is. In CPython, vóór versie 3.2, zou de lopende thread na een bepaald aantal python-instructies controleren of een andere code het slot wilde (dat wil zeggen, het slot ontgrendelde en vervolgens opnieuw opvroeg). Deze methode veroorzaakte de neiging om de draad te laten verhongeren, grotendeels omdat de draad die het slot ontgrendelde het opnieuw zou verwerven voordat de wachtende draden de kans hadden om wakker te worden. Sinds 3.2 wachten threads die de GIL willen hebben enige tijd op het slot, en na die tijd stellen ze een gedeelde variabele in die de lopende thread dwingt om op te geven. Dit kan echter nog steeds leiden tot drastisch langere uitvoeringstijden. Zie de onderstaande links van dabeaz.com (in de sectie referenties) voor meer informatie.

CPython geeft de GIL automatisch vrij wanneer een thread een I / O-bewerking uitvoert. Beeldverwerkingsbibliotheken en aantal rekenkundige bewerkingen geven de GIL vrij voordat ze worden verwerkt.

Voordelen van de GIL

Voor tolken die de GIL gebruiken, is de GIL systemisch. Het wordt gebruikt om de status van de applicatie te behouden. Voordelen zijn onder meer:
  • Garbage collection - thread-safe referentietellingen moeten worden aangepast terwijl de GIL is vergrendeld. In CPython is alle garbarge-collecties gekoppeld aan de GIL. Dit is een grote; zie het python.org wiki-artikel over de GIL (vermeld in Referenties, hieronder) voor details over wat nog functioneel moet zijn als iemand de GIL wil verwijderen.
  • Gemakkelijk voor programmeurs die te maken hebben met de GIL - alles vergrendelen is simplistisch, maar gemakkelijk te coderen
  • Vergemakkelijkt het importeren van modules uit andere talen

Gevolgen van de GIL

De GIL staat slechts één thread toe om python-code tegelijk in de python-interpreter uit te voeren. Dit betekent dat multithreading van processen die strikte python-code uitvoeren gewoon niet werkt. Wanneer u threads tegen de GIL gebruikt, zult u waarschijnlijk slechter presteren met de threads dan wanneer u in een enkele thread zou lopen.

Referenties:

https://wiki.python.org/moin/GlobalInterpreterLock - snelle samenvatting van wat het doet, fijne details over alle voordelen

http://programmers.stackexchange.com/questions/186889/why-was-python-written-with-the-gil - duidelijk geschreven samenvatting

http://www.dabeaz.com/python/UnderstandingGIL.pdf - hoe de GIL werkt en waarom deze op meerdere cores vertraagt

http://www.dabeaz.com/GIL/gilvis/index.html - visualisatie van de gegevens die laten zien hoe de GIL threads blokkeert

http://jeffknupp.com/blog/2012/03/31/pythons-hardest-problem/ - eenvoudig te begrijpen geschiedenis van het GIL-probleem

https://jeffknupp.com/blog/2013/06/30/pythons-hardest-problem-revisited/ - details over manieren om de beperkingen van de GIL te omzeilen

Multiprocessing.Pool

Het eenvoudige antwoord bij het vragen hoe threads in Python te gebruiken is: "Niet doen. Gebruik in plaats daarvan processen." Met de multiprocessing-module kunt u processen maken met dezelfde syntaxis als het maken van threads, maar ik gebruik liever hun handige Pool-object.

Met behulp van de code die David Beazley voor het eerst gebruikte om de gevaren van threads tegen de GIL te tonen , zullen we het herschrijven met behulp van multiprocessing .

David Beazley's code die GIL threading problemen liet zien

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
Herschreven met multiprocessing.
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)

In plaats van threads te maken, creëert dit nieuwe processen. Omdat elk proces zijn eigen tolk is, zijn er geen GIL-botsingen. multiprocessing.Pool opent zoveel processen als er cores op de machine zijn, hoewel in het bovenstaande voorbeeld er slechts twee nodig zijn. In een realistisch scenario wilt u uw lijst zo ontwerpen dat deze minstens even lang is als er processors op uw machine staan. De pool voert de functie uit die u hem bij elk argument wilt laten uitvoeren, tot het aantal processen dat wordt gecreëerd. Wanneer de functie is voltooid, worden alle resterende functies in de lijst op dat proces uitgevoerd.

Ik heb ontdekt dat, zelfs met de instructie with , de processen blijven bestaan als je niet sluit en je bij de pool aansluit. Om bronnen op te schonen, sluit en sluit ik altijd aan bij mijn pools.

Cython nogil:

Cython is een alternatieve python-tolk. Het gebruikt de GIL, maar laat je het uitschakelen. Zie hun documentatie

Als voorbeeld gebruiken we de code die David Beazley voor het eerst gebruikte om de gevaren van threads tegen de GIL aan te tonen , en herschrijven we deze met nogil:

David Beazley's code die GIL threading problemen liet zien

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

Herschreven met nogil (ALLEEN WERKT IN 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

Zo eenvoudig is het zolang je cython gebruikt. Merk op dat de documentatie zegt dat je ervoor moet zorgen dat je geen python-objecten wijzigt:

Code in de hoofdtekst van de instructie mag op geen enkele manier Python-objecten manipuleren en mag niets aanroepen dat Python-objecten manipuleert zonder eerst de GIL opnieuw te verwerven. Cython controleert dit momenteel niet.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow