Co to jest globalna blokada tłumacza (GIL) w CPython?

244

Co to jest globalna blokada tłumacza i dlaczego stanowi problem?

Podczas usuwania GIL z Pythona powstało wiele hałasu i chciałbym zrozumieć, dlaczego jest to takie ważne. Nigdy sam nie napisałem kompilatora ani tłumacza, więc nie bądź oszczędny w szczegółach, prawdopodobnie potrzebuję ich do zrozumienia.

e-satis
źródło
3
Zobacz, jak David Beazley mówi ci wszystko, co chciałeś wiedzieć o GIL.
hughdbrown
1
Oto długi artykuł mówiący o GIL i wątkach w Pythonie, który jakiś czas temu napisałem. Mówi o tym dość szczegółowo: jessenoller.com/2009/02/01/…
jnoller
Oto kod ilustrujący działanie GIL: github.com/cankav/python_gil_demonstration
Can Kavaklıoğlu
3
Uważam, że to najlepsze wyjaśnienie GIL. Proszę przeczytaj. dabeaz.com/python/UnderstandingGIL.pdf
suhao399
realpython.com/python-gil Uważam to za przydatne
qwr

Odpowiedzi:

220

GIL Pythona ma na celu serializację dostępu do wewnętrznych elementów interpretera z różnych wątków. W systemach wielordzeniowych oznacza to, że wiele wątków nie może skutecznie wykorzystywać wielu rdzeni. (Gdyby GIL nie doprowadził do tego problemu, większość ludzi nie przejmowałaby się GIL - problem ten jest podnoszony tylko z powodu rosnącej częstości występowania systemów wielordzeniowych.) Jeśli chcesz to szczegółowo zrozumieć, możesz obejrzeć ten film lub obejrzeć ten zestaw slajdów . To może być za dużo informacji, ale poprosiłeś o szczegóły :-)

Zauważ, że GIL Pythona jest tak naprawdę tylko problemem dla CPython, implementacji referencyjnej. Jython i IronPython nie mają GIL. Jako programista w Pythonie ogólnie nie spotykasz GIL, chyba że piszesz rozszerzenie C. Autorzy rozszerzeń C muszą zwolnić GIL, gdy ich rozszerzenia blokują operacje wejścia / wyjścia, aby inne wątki w procesie Pythona miały szansę na uruchomienie.

Vinay Sajip
źródło
46
Dobra odpowiedź - w zasadzie oznacza to, że wątki w Pythonie są dobre tylko do blokowania I / O; Twoja aplikacja nigdy nie przekroczy 1 rdzenia procesora
Ana Betts
8
„Jako programista Pythona zazwyczaj nie spotykasz GIL, chyba że piszesz rozszerzenie C” - możesz nie wiedzieć, że przyczyną twojego wielowątkowego kodu działającego w ślimaczym tempie jest GIL, ale „ Na pewno poczuję jego efekty. Nadal mnie zadziwia, że ​​aby skorzystać z 32-rdzeniowego serwera z Pythonem, potrzebuję 32 procesów z całym związanym z tym narzutem.
Podstawowy
6
@PaulBetts: to nieprawda. Jest prawdopodobne, że wydajność kod C krytyczny już korzysta z rozszerzeń, które mogą i uwalniają Gil przykład regex, lxml, numpymodułów. Cython pozwala na wydanie GIL w niestandardowym kodzie, np.b2a_bin(data)
jfs
5
@Paul Betts: Możesz uzyskać ponad 1 kod procesora użycia procesora za pomocą modułu wieloprocesowego . Tworzenie wielu procesów jest „cięższe” niż tworzenie wielu wątków, ale jeśli naprawdę potrzebujesz równoległej pracy, w pythonie jest to opcja.
AJNeufeld
1
@david_adler Tak, nadal tak jest i prawdopodobnie pozostanie jeszcze przez jakiś czas. To tak naprawdę nie sprawiło, że Python jest naprawdę przydatny do wielu różnych obciążeń.
Vinay Sajip
59

Załóżmy, że masz wiele wątków, które tak naprawdę nie dotykają się nawzajem. Powinny one być wykonywane tak niezależnie, jak to możliwe. Jeśli masz „globalną blokadę”, którą musisz zdobyć, aby (powiedzmy) wywołać funkcję, która może skończyć się wąskim gardłem. Możesz skończyć, nie mając wiele korzyści z posiadania wielu wątków.

Aby umieścić to w prawdziwej analogii: wyobraź sobie 100 programistów pracujących w firmie z jednym kubkiem kawy. Większość programistów spędza czas na czekaniu na kawę zamiast kodowania.

Żadna z tych rzeczy nie jest specyficzna dla Pythona - nie znam szczegółów, po co Python potrzebował GIL. Mam jednak nadzieję, że daje to lepszy obraz ogólnej koncepcji.

Jon Skeet
źródło
Tyle że czekanie na kubek kawy wydaje się dość procesem związanym z operacjami wejścia / wyjścia, ponieważ z pewnością mogą robić inne rzeczy, czekając na kubek. GIL ma bardzo niewielki wpływ na ciężkie wątki we / wy, które i tak spędzają większość czasu.
Cruncher
36

Najpierw zrozumiemy, co zapewnia GIL w Pythonie:

Każda operacja / instrukcja jest wykonywana w tłumaczu. GIL zapewnia, że ​​tłumacz jest utrzymywany przez pojedynczy wątek w określonym momencie . Twój program pythonowy z wieloma wątkami działa w jednym tłumaczu. W dowolnym momencie tłumacz ten jest utrzymywany przez jeden wątek. Oznacza to, że tylko wątek, w którym znajduje się tłumacz, działa w dowolnym momencie .

Dlaczego to jest problem:

Twoja maszyna może mieć wiele rdzeni / procesorów. I wiele rdzeni pozwala na jednoczesne wykonywanie wielu wątków, tzn. Wiele wątków może być wykonywanych w dowolnym momencie. . Ale ponieważ interpreter jest utrzymywany przez jeden wątek, inne wątki nic nie robią, nawet jeśli mają dostęp do rdzenia. Tak więc, nie otrzymujesz żadnej korzyści zapewnianej przez wiele rdzeni, ponieważ w dowolnym momencie używany jest tylko jeden rdzeń, który jest rdzeniem używanym przez wątek, w którym aktualnie znajduje się interpreter. Tak więc wykonanie programu potrwa tak długo, jak gdyby był to program jednowątkowy.

Jednak potencjalnie blokujące lub długotrwałe operacje, takie jak operacje we / wy, przetwarzanie obrazu i niszczenie liczb NumPy, zdarzają się poza GIL. Zabrano stąd . W przypadku takich operacji operacja wielowątkowa będzie nadal szybsza niż pojedyncza operacja wątkowa, pomimo obecności GIL. Tak więc GIL nie zawsze jest wąskim gardłem.

Edycja: GIL to szczegół implementacji CPython. IronPython i Jython nie mają GIL, więc powinien być w nich naprawdę wielowątkowy program, myślałem, że nigdy nie korzystałem z PyPy i Jython i nie jestem tego pewien.

Akshar Raaj
źródło
4
Uwaga : PyPy ma GIL . Odniesienie : http://doc.pypy.org/en/latest/faq.html#does-pypy-have-a-gil-why . Podczas gdy Ironpython i Jython nie mają GIL.
Tasdik Rahman,
Rzeczywiście, PyPy ma GIL, ale IronPython nie.
Emmanuel,
@Emmanuel Edytował odpowiedź, aby usunąć PyPy i dołączyć IronPython.
Akshar Raaj,
17

Python nie pozwala na wielowątkowość w najprawdziwszym tego słowa znaczeniu. Ma pakiet wielowątkowy, ale jeśli chcesz wielowątkowy, aby przyspieszyć swój kod, zwykle nie jest dobrym pomysłem, aby go użyć. Python ma konstrukcję o nazwie Global Interpreter Lock (GIL).

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

GIL zapewnia, że ​​tylko jeden z twoich „wątków” może być wykonywany jednocześnie. Wątek nabywa GIL, wykonuje trochę pracy, a następnie przekazuje GIL do następnego wątku. Dzieje się to bardzo szybko, więc dla ludzkiego oka może się wydawać, że twoje wątki wykonują się równolegle, ale tak naprawdę po prostu korzystają z tego samego rdzenia procesora. Całe to przekazywanie GIL zwiększa koszty wykonania. Oznacza to, że jeśli chcesz, aby Twój kod działał szybciej, korzystanie z pakietu wątków często nie jest dobrym pomysłem.

Istnieją powody, aby używać pakietu wątków Pythona. Jeśli chcesz uruchomić kilka rzeczy jednocześnie, a wydajność nie jest problemem, to jest całkowicie w porządku i wygodne. Lub jeśli uruchamiasz kod, który musi na coś czekać (jak niektóre IO), może to mieć sens. Ale biblioteka wątków nie pozwoli na użycie dodatkowych rdzeni procesora.

Wielowątkowość może być zlecona na zewnątrz systemu operacyjnego (poprzez przetwarzanie wieloskładnikowe), niektórych zewnętrznych aplikacji, które wywołują Twój kod Python (np. Spark lub Hadoop), lub jakiegoś kodu, który wywołuje Twój kod Python (np .: możesz mieć swój Python wywołanie kodu funkcji C, która wykonuje kosztowne wielowątkowe operacje).

Ijaz Ahmad Khan
źródło
15

Ilekroć dwa wątki mają dostęp do tej samej zmiennej, masz problem. Na przykład w C ++ sposobem na uniknięcie tego problemu jest zdefiniowanie jakiejś blokady mutex, aby zapobiec dwóm wątkom, aby, powiedzmy, wejść do obiektu ustawiającego obiekt w tym samym czasie.

Wielowątkowość jest możliwa w Pythonie, ale dwa wątki nie mogą być wykonywane w tym samym czasie z większą dokładnością niż jedna instrukcja Pythona. Działający wątek otrzymuje blokadę globalną o nazwie GIL.

Oznacza to, że jeśli zaczniesz pisać kod wielowątkowy, aby skorzystać z procesora wielordzeniowego, wydajność się nie poprawi. Zwykłym obejściem jest przejście na wiele procesów.

Pamiętaj, że możesz zwolnić GIL, jeśli jesteś w metodzie, którą napisałeś na przykład w C.

Korzystanie z GIL nie jest nieodłączne od Pythona, ale z niektórych jego interpreterów, w tym z najczęstszym CPython. (#edytowane, patrz komentarz)

Problem GIL jest nadal aktualny w Pythonie 3000.

fulmikoton
źródło
Stackless nadal ma GIL. Stackless nie poprawia wątków (jak w module) - oferuje inną metodę programowania (coroutines), które próbują rozwiązać problem, ale wymagają funkcji nieblokujących.
jnoller,
Co z nowym GIL w 3.2?
new123456
Wystarczy dodać, że nie masz problemu / potrzebujesz muteksów / semaforów, jeśli tylko jeden wątek zaktualizuje pamięć. @ new123456 zmniejsza rywalizację i lepiej planuje wątki bez uszczerbku dla wydajności jednowątkowej (co samo w sobie jest imponujące), ale nadal jest globalną blokadą.
Podstawowy
14

Dokumentacja Python 3.7

Chciałbym również podkreślić następujący cytat z dokumentacji Pythonathreading :

Szczegóły implementacji CPython: W CPython, z powodu globalnej blokady interpretera, tylko jeden wątek może wykonać kod Pythona jednocześnie (nawet jeśli niektóre biblioteki zorientowane na wydajność mogą obejść to ograniczenie). Jeśli chcesz, aby aplikacja lepiej wykorzystywała zasoby obliczeniowe maszyn wielordzeniowych, zalecamy użycie multiprocessinglub concurrent.futures.ProcessPoolExecutor. Jednak wątkowanie jest nadal odpowiednim modelem, jeśli chcesz uruchamiać wiele zadań związanych z operacjami we / wy jednocześnie.

To łącze do pozycji Glosariusz, wglobal interpreter lock której wyjaśniono, że GIL sugeruje, że równoległość wątkowa w Pythonie jest nieodpowiednia do zadań związanych z procesorem :

Mechanizm używany przez interpreter CPython w celu zapewnienia, że ​​tylko jeden wątek wykonuje kod bajtowy Pythona jednocześnie. Upraszcza to implementację CPython, czyniąc model obiektowy (w tym krytyczne typy wbudowane, takie jak dict), domyślnie zabezpieczony przed równoczesnym dostępem. Zablokowanie całego interpretera ułatwia interpreterowi wielowątkowość kosztem dużej równoległości zapewnianej przez maszyny wieloprocesorowe.

Jednak niektóre moduły rozszerzeń, standardowe lub zewnętrzne, zostały zaprojektowane tak, aby zwolnić GIL podczas wykonywania zadań wymagających dużej mocy obliczeniowej, takich jak kompresja lub mieszanie. Ponadto GIL jest zawsze zwalniany podczas wykonywania operacji we / wy.

Dotychczasowe wysiłki w celu stworzenia interpretera „bez wątków” (takiego, który blokuje współdzielone dane z większą szczegółowością) nie powiodły się, ponieważ w zwykłym przypadku jednoprocesorowym wydajność spadła. Uważa się, że przezwyciężenie tego problemu z wydajnością spowodowałoby, że wdrożenie byłoby znacznie bardziej skomplikowane, a zatem kosztowne w utrzymaniu.

Ten cytat sugeruje również, że dyktowania, a tym samym przypisywanie zmiennych, są również wątkowo bezpieczne jako szczegóły implementacji CPython:

Następnie dokumentacja multiprocessingpakietu wyjaśnia, w jaki sposób pokonuje GIL poprzez proces odradzania, ujawniając interfejs podobny do threading:

multiprocessing to pakiet, który obsługuje procesy odradzania przy użyciu interfejsu API podobnego do modułu wątków. Pakiet wieloprocesowy oferuje zarówno lokalną, jak i zdalną współbieżność, skutecznie omijając blokadę globalnego interpretera, używając podprocesów zamiast wątków. Z tego powodu moduł wieloprocesowy pozwala programiście w pełni wykorzystać wiele procesorów na danym komputerze. Działa zarówno w systemie Unix, jak i Windows.

I dokumentacja dlaconcurrent.futures.ProcessPoolExecutor wyjaśnienia, że wykorzystuje multiprocessingjako backend:

Klasa ProcessPoolExecutor jest podklasą Executora, która wykorzystuje pulę procesów do asynchronicznego wykonywania wywołań. ProcessPoolExecutor wykorzystuje moduł wieloprocesowy, który pozwala mu przejść obok globalnej blokady interpretera, ale oznacza również, że można wykonywać i zwracać tylko obiekty do wyboru.

które powinny być skontrastowane z inną klasą bazową, ThreadPoolExecutorktóra używa wątków zamiast procesów

ThreadPoolExecutor jest podklasą Executora, która wykorzystuje pulę wątków do asynchronicznego wykonywania wywołań.

z którego dochodzimy do wniosku, że ThreadPoolExecutorjest odpowiedni tylko do zadań związanych z We / Wy, a jednocześnie ProcessPoolExecutormoże obsługiwać zadania związane z procesorem.

Poniższe pytanie dotyczy przede wszystkim tego, dlaczego GIL istnieje: Dlaczego blokada globalnego interpretera?

Eksperymenty procesowe a wątkowe

W Multiprocessing vs Threading Python przeprowadziłem eksperymentalną analizę procesu vs wątków w Pythonie.

Szybki podgląd wyników:

wprowadź opis zdjęcia tutaj

Ciro Santilli
źródło
0

Dlaczego Python (CPython i inni) korzysta z GIL

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

W CPython globalna blokada interpretera (GIL) to muteks, który uniemożliwia wielu wątkom natywnym wykonywanie bajtów kodu Pythona jednocześnie. Ta blokada jest konieczna głównie dlatego, że zarządzanie pamięcią CPython nie jest bezpieczne dla wątków.

Jak usunąć go z Pythona?

Podobnie jak Lua, może Python mógłby uruchomić wiele maszyn wirtualnych, ale Python tego nie robi, myślę, że powinny istnieć inne powody.

W Numpy lub w innej rozszerzonej bibliotece Pythona czasami udostępnienie GIL innym wątkom może zwiększyć wydajność całego programu.

maoyang
źródło
0

Chcę podzielić się przykładem z wielowątkowości książki dotyczącej efektów wizualnych. Oto klasyczna sytuacja martwego zamka

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

Teraz rozważ zdarzenia w sekwencji powodujące zakleszczenie.

╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
    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                      
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝
użytkownik1767754
źródło