Sök…


Anmärkningar

Varför finns det en GIL?

GIL har funnits i CPython sedan starten av Python-trådar, 1992. Den är utformad för att säkerställa gängsäkerhet för att köra python-kod. Python-tolkar skrivna med en GIL förhindrar att flera infödda trådar kör Python-bytekoder på en gång. Detta gör det enkelt för plugins att se till att deras kod är trådsäker: lås helt enkelt GIL, och bara din aktiva tråd kan köras, så din kod är automatiskt trådsäker.

Kort version: GIL ser till att oavsett hur många processorer och trådar du har, bara en tråd av en python-tolk körs samtidigt.

Detta har många användarvänliga fördelar, men har också många negativa fördelar.

Observera att en GIL inte är ett krav på Python-språket. Följaktligen kan du inte komma åt GIL direkt från standardpytonkod. Inte alla implementeringar av Python använder en GIL.

Tolkar som har en GIL: CPython, PyPy, Cython (men du kan inaktivera GIL med nogil )

Tolkar som inte har en GIL: Jython, IronPython

Detaljer om hur GIL fungerar:

När en tråd löper låser den GIL. När en tråd vill köra begär den GIL och väntar tills den är tillgänglig. I CPython, före version 3.2, skulle den löpande tråden kontrollera efter ett visst antal pythoninstruktioner för att se om annan kod ville ha låset (det vill säga den släppte låset och begärde det igen). Denna metod tenderade att orsaka trådsvält, till stor del för att tråden som släppte låset skulle få det igen innan de väntande trådarna hade en chans att vakna. Sedan 3.2 väntar trådar som vill ha GIL på låset under en tid, och efter den tiden ställer de in en delad variabel som tvingar den löpande tråden att ge. Detta kan dock fortfarande resultera i drastiskt längre körningstider. Se länkarna nedan från dabeaz.com (i referensavsnittet) för mer information.

CPython släpper automatiskt GIL när en tråd utför en I / O-operation. Bildbearbetningsbibliotek och numpy number crunching-operationer frisätter GIL innan de behandlas.

Fördelarna med GIL

För tolkar som använder GIL är GIL systemiskt. Det används för att bevara applikationens tillstånd. Fördelarna inkluderar:
  • Avfallssamling - gängsäkra referensräkningar måste ändras medan GIL är låst. I CPython är all samlingssamling bunden till GIL. Detta är en stor; se python.org wiki-artikeln om GIL (listad i referenser nedan) för detaljer om vad som fortfarande måste fungera om man ville ta bort GIL.
  • Lätt för programmerare som arbetar med GIL - att låsa allt är förenklat, men lätt att koda
  • Tar bort importen av moduler från andra språk

Konsekvenser av GIL

GIL tillåter bara en tråd att köra pythonkod åt gången inuti pytontolkaren. Detta innebär att flertrådning av processer som kör strikt pytonkod helt enkelt inte fungerar. När du använder trådar mot GIL kommer du förmodligen att ha sämre prestanda med trådarna än om du sprang i en enda tråd.

referenser:

https://wiki.python.org/moin/GlobalInterpreterLock - snabb sammanfattning av vad den gör, fina detaljer om alla fördelar

http://programmers.stackexchange.com/questions/186889/why-was-python-written-med-the-gil - tydligt skriven sammanfattning

http://www.dabeaz.com/python/UnderstandingGIL.pdf - hur GIL fungerar och varför det bromsar ner på flera kärnor

http://www.dabeaz.com/GIL/gilvis/index.html - visualisering av data som visar hur GIL låser upp trådar

http://jeffknupp.com/blog/2012/03/31/pythons-hardest-problem/ - enkel att förstå GIL-problemets historia

https://jeffknupp.com/blog/2013/06/30/pythons-hardest-problem-revisited/ - detaljer om sätt att arbeta runt GIL: s begränsningar

Multiprocessing.Pool

Det enkla svaret när man frågar hur man använder trådar i Python är: "Inte. Använd processer istället." Multiprocessing-modulen låter dig skapa processer med liknande syntax som att skapa trådar, men jag föredrar att använda deras bekväma Pool-objekt.

Med hjälp av koden som David Beazley först använde för att visa farorna med trådar mot GIL , kommer vi att skriva om den med multiprocessing.Pool :

David Beazleys kod som visade GIL-trådproblem

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
Omskrivet med 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)

Istället för att skapa trådar skapar detta nya processer. Eftersom varje process är sin egen tolk finns det inga GIL-kollisioner. multiprocessing.Pool öppnar så många processer som det finns kärnor på maskinen, men i exemplet ovan skulle det bara behöva två. I ett verkligt scenario vill du utforma din lista så att den har minst lika lång längd som det finns processorer på din maskin. Poolen kommer att köra den funktion du säger att den ska köra med varje argument, upp till antalet processer den skapar. När funktionen är klar körs alla återstående funktioner i listan på den processen.

Jag har funnit att även med hjälp av with uttalandet, om du inte stänger och går med i poolen, fortsätter processerna att existera. För att rensa upp resurser stänger jag alltid och går med i mina pooler.

Cython nogil:

Cython är en alternativ pytontolk. Den använder GIL, men låter dig inaktivera den. Se deras dokumentation

Som exempel, genom att använda koden som David Beazley först använde för att visa farorna med trådar mot GIL , kommer vi att skriva om den med nogil:

David Beazleys kod som visade GIL-trådproblem

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

Omskrivet med hjälp av nogil (ENDAST ARBETE I 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

Det är så enkelt, så länge du använder cython. Observera att dokumentationen säger att du måste se till att du inte ändrar några pythonobjekt:

Kod i uttalandets kropp får inte manipulera Python-objekt på något sätt och får inte kalla något som manipulerar Python-objekt utan att först återfå GIL. Cython kontrollerar för närvarande inte detta.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow