Python Language
Praca wokół globalnej blokady tłumacza (GIL)
Szukaj…
Uwagi
Dlaczego istnieje GIL?
GIL istnieje w CPython od początku wątków Python w 1992 roku. Został zaprojektowany w celu zapewnienia bezpieczeństwa wątków podczas uruchamiania kodu Python. Interpretatory języka Python napisane przy użyciu GIL zapobiegają jednoczesnemu wykonywaniu kodów bajtowych Pythona przez wiele natywnych wątków. Ułatwia to wtyczkom upewnienie się, że ich kod jest bezpieczny dla wątków: wystarczy zablokować GIL, a tylko twój aktywny wątek jest w stanie działać, więc twój kod jest automatycznie bezpieczny dla wątków.Wersja skrócona: GIL zapewnia, że bez względu na to, ile procesorów i wątków masz, tylko jeden wątek interpretera Pythona będzie działał jednocześnie.
Ma to wiele zalet związanych z łatwością użycia, ale ma również wiele negatywnych zalet.
Zauważ, że GIL nie jest wymaganiem języka Python. W związku z tym nie można uzyskać dostępu do GIL bezpośrednio ze standardowego kodu python. Nie wszystkie implementacje Pythona używają GIL.
Tłumacze posiadający GIL: CPython, PyPy, Cython (ale możesz wyłączyć GIL za pomocą nogil
)
Tłumacze, którzy nie mają GIL: Jython, IronPython
Szczegółowe informacje na temat działania GIL:
Gdy wątek działa, blokuje GIL. Gdy wątek chce się uruchomić, żąda GIL i czeka, aż będzie dostępny. W CPython przed wersją 3.2 działający wątek sprawdzałby po określonej liczbie instrukcji python, czy inny kod chce blokady (to znaczy zwolnił blokadę, a następnie poprosił o nią ponownie). Ta metoda powodowała głód nici, głównie dlatego, że nić zwalniająca blokadę nabyła ją ponownie, zanim oczekujące wątki miały szansę się obudzić. Od wersji 3.2 wątki, które chcą GIL, czekają na blokadę przez pewien czas, a po tym czasie ustawiają zmienną wspólną, która wymusza ustępowanie działającego wątku. Może to jednak skutkować znacznie dłuższymi czasami wykonania. Zobacz poniższe linki z dabeaz.com (w sekcji referencji), aby uzyskać więcej informacji.CPython automatycznie zwalnia GIL, gdy wątek wykonuje operację We / Wy. Biblioteki przetwarzania obrazu i operacje na liczbach liczbowych zwalniają GIL przed rozpoczęciem przetwarzania.
Korzyści z GIL
W przypadku tłumaczy, którzy używają GIL, GIL ma charakter systemowy. Służy do zachowania stanu aplikacji. Korzyści obejmują:- Odśmiecanie - liczby bezpiecznych wątków muszą być modyfikowane, gdy GIL jest zablokowany. W CPython cała kolekcja Garbarge jest powiązana z GIL. Ten jest duży; zobacz artykuł wiki python.org na temat GIL (wymieniony w odnośnikach poniżej), aby uzyskać szczegółowe informacje na temat tego, co musi nadal działać, jeśli ktoś chce usunąć GIL.
- Łatwość dla programistów zajmujących się GIL - blokowanie wszystkiego jest proste, ale łatwe do kodowania
- Ułatwia import modułów z innych języków
Konsekwencje GIL
GIL pozwala tylko jednemu wątkowi na uruchamianie kodu Pythona jednocześnie w interpreterie Pythona. Oznacza to, że wielowątkowość procesów uruchamiających ścisły kod python po prostu nie działa. Używając wątków przeciwko GIL, prawdopodobnie będziesz mieć gorszą wydajność z wątkami niż w przypadku uruchomienia pojedynczego wątku.Bibliografia:
https://wiki.python.org/moin/GlobalInterpreterLock - szybkie podsumowanie tego, co robi, dokładne informacje o wszystkich korzyściach
http://programmers.stackexchange.com/questions/186889/why-was-python-written-with-the-gil - jasne streszczenie
http://www.dabeaz.com/python/UnderstandingGIL.pdf - jak działa GIL i dlaczego spowalnia wiele rdzeni
http://www.dabeaz.com/GIL/gilvis/index.html - wizualizacja danych pokazująca, jak GIL blokuje wątki
http://jeffknupp.com/blog/2012/03/31/pythons-hardest-problem/ - prosta do zrozumienia historia problemu GIL
https://jeffknupp.com/blog/2013/06/30/pythons-hardest-problem-revisited/ - szczegółowe informacje na temat sposobów obejścia ograniczeń GIL
Multiprocessing.Pool
Prosta odpowiedź na pytanie, jak używać wątków w Pythonie, brzmi: „Nie. Zamiast tego używaj procesów”. Moduł wieloprocesowy pozwala tworzyć procesy o podobnej składni jak tworzenie wątków, ale wolę używać ich wygodnego obiektu Pool.
Korzystając z kodu, którego David Beazley użył po raz pierwszy do pokazania niebezpieczeństw związanych z wątkami przeciwko GIL , przepisujemy go za pomocą przetwarzania wieloprocesowego .
Kod Davida Beazleya pokazujący problemy z wątkami 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
Ponownie napisane przy użyciu przetwarzania wieloprocesowego. Pula: 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)
Zamiast tworzyć wątki, tworzy to nowe procesy. Ponieważ każdy proces ma własny interpretator, nie ma kolizji GIL. multiprocessing.Pool otworzy tyle procesów, ile jest rdzeni na maszynie, chociaż w powyższym przykładzie wystarczyłyby tylko dwa. W rzeczywistym scenariuszu chcesz zaprojektować listę tak, aby miała co najmniej tyle samo długości, ile procesorów na twoim komputerze. Pula uruchomi funkcję, którą każesz uruchamiać z każdym argumentem, aż do liczby procesów, które utworzy. Po zakończeniu funkcji wszystkie pozostałe funkcje na liście zostaną uruchomione w tym procesie.
Przekonałem się, że nawet przy użyciu instrukcji with
, jeśli nie zamkniesz i nie dołączysz do puli, procesy nadal będą istnieć. Aby oczyścić zasoby, zawsze zamykam i dołączam do swoich pul.
Cython nogil:
Cython to alternatywny interpreter języka Python. Używa GIL, ale pozwala go wyłączyć. Zobacz ich dokumentację
Na przykład, używając kodu, którego David Beazley po raz pierwszy użył do pokazania niebezpieczeństw związanych z wątkami przeciwko GIL , przepisujemy go za pomocą nogil:
Kod Davida Beazleya pokazujący problemy z wątkami 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
Przepisane przy użyciu nogil (TYLKO DZIAŁA W CYTHONIE):
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
To takie proste, o ile używasz cytonu. Zauważ, że dokumentacja mówi, że musisz upewnić się, że nie zmieniasz żadnych obiektów Pythona:
Kod w treści instrukcji nie może w żaden sposób manipulować obiektami Python i nie może wywoływać niczego, co manipuluje obiektami Python bez uprzedniego ponownego uzyskania GIL. Cython obecnie tego nie sprawdza.