Python jest jednym z najpopularniejszych języków programowania. Kochamy go za prostą składnię i ogrom bibliotek. Jednocześnie od lat budzi emocje jeden element: Global Interpreter Lock (GIL). W skrócie to mechanizm, który w danym momencie dopuszcza do wykonania tylko jeden wątek kodu Pythona. W tym tekście wyjaśniam, skąd wziął się GIL, dlaczego wciąż istnieje, kiedy przeszkadza, a kiedy nie ma znaczenia. Na koniec zobaczysz, co zmienia PEP 703 i tryb „free-threaded” w nowszych wersjach Pythona.
GIL to mechanizm synchronizacji w interpreterze CPython (najpopularniejszej implementacji Pythona), który sprawia, że w danym momencie tylko jeden wątek może wykonywać kod Pythona. Nawet jeśli masz komputer z 16 rdzeniami, wątkowość w Pythonie (przynajmniej w kontekście zadań CPU-bound, czyli mocno obciążających procesor) będzie działać tak, jakby wszystko wykonywało się na jednym rdzeniu.
Można to porównać do jednego mikrofonu na konferencji – nawet jeśli w sali jest 100 osób chętnych do wypowiedzi (wątki), tylko jedna osoba (wątek) może mówić do mikrofonu (wykonywać kod Pythona) w danym momencie. Reszta musi poczekać na swoją kolej.
Wielowątkowość to dzielenie pracy jednego procesu na kilka wątków. Zwykle pomaga lepiej wykorzystać wiele rdzeni. Przykład z życia? Restauracyjna kuchnia: wielu kucharzy przygotowuje równolegle różne potrawy, więc dania powstają szybciej. Jednak w CPythonie działa „jeden mikrofon”: GIL wpuszcza do CPU tylko jeden wątek wykonujący kod Pythona naraz. W efekcie zadania CPU-bound nie przyspieszają na wielu wątkach, choć zadań I/O-bound dotyczy to dużo mniej.
GIL uproszcza i zabezpiecza zarządzanie pamięcią. CPython używa zliczania referencji: każdy obiekt ma licznik użyć. Gdy licznik spada do zera, interpreter może obiekt usunąć. Bez GIL kilka wątków mogłoby jednocześnie zmieniać ten sam licznik, co prowadziłoby do trudnych wyścigów danych. Dzięki GIL implementacja jest prostsza, a programy stabilniejsze.
GIL nie zawsze jest problemem – wszystko zależy od rodzaju zadań:
TL;DR
Przykład:
import threading
import time
start = time.time()
def worker():
count = 0
for i in range(10**7):
count += 1
threads = []
for _ in range(4): # próbujemy 4 wątki
t = threading.Thread(target=worker)
t.start()
threads.append(t)
for t in threads:
t.join()
print("Czas wykonania:", time.time() - start)
Niezależnie od liczby rdzeni, ten kod zakończy się prawie w tym samym czasie, jakby działał w pojedynczym wątku.
Na szczęście mamy kilka bezpiecznych sposobów na obejście GIL. Po pierwsze, dla obliczeń wybierz multiprocessing. Po drugie, przy I/O rozważ asyncio. Wreszcie, do ciężkiej matematyki użyj NumPy lub bibliotek C/C++. Poniżej najpopularniejsze podejścia:
from multiprocessing import Pool def worker(n): return sum(i*i for i in range(n)) if __name__ == "__main__": with Pool(4) as p: print(p.map(worker, [10**6, 10**6, 10**6, 10**6]))I/O: preferuj asyncio lub wątki – GIL rzadko będzie wąskim gardłem.
CPU: preferuj multiprocessing albo biblioteki w C/NumPy – realnie wykorzystasz wiele rdzeni.
Miks I/O + CPU: łącz asyncio (sieć) z procesami dla najcięższych obliczeń.
Od lat społeczność dyskutuje, czy Python powinien pozbyć się GIL. Konkretem jest PEP 703: w Pythonie 3.13 pojawia się eksperymentalny tryb bez GIL (free-threaded). Co ważne, klasyczny tryb z GIL pozostanie, by zachować kompatybilność i prostotę.
Podsumowując, w najbliższym czasie będziemy mieć obie opcje. Dzięki temu możesz wybrać tryb dopasowany do projektu i etapami migrować kod.
To oznacza nowy etap rozwoju Pythona. Być może za kilka lat GIL stanie się tylko trybem zgodności. W rezultacie większość aplikacji będzie mogła działać naprawdę wielowątkowo.
Global Interpreter Lock to jedno z najbardziej kontrowersyjnych rozwiązań w Pythonie. Ogranicza pełne wykorzystanie wielowątkowości, ale chroni programistów przed skomplikowanymi błędami związanymi z zarządzaniem pamięcią. Dzięki temu Python pozostaje prosty i stabilny.
Na szczęście istnieją sprawdzone metody obejścia problemu – multiprocessing, biblioteki w C oraz asynchroniczność. A co najważniejsze – przyszłość wygląda obiecująco. Python 3.13 i PEP 703 mogą sprawić, że GIL przestanie być zmorą programistów i stanie się tylko historią.
Dla początkujących programistów warto zapamiętać: Python nie jest wolny przez GIL – trzeba tylko świadomie dobierać narzędzia. Do zadań I/O sprawdza się znakomicie, do obliczeń CPU warto używać multiprocessing lub NumPy. A w przyszłości – być może całkowicie pożegnamy GIL 🙂