Nysnø: Pythons kontroversielle GIL 🔥
Tidlig på 90 tallet gjorde Guido Van Rossum en endring i Python’s kodebase som skulle få store konsekvenser. Mekanismen han introduserte har fått mye av æren for at Python er så populært som det er i dag, men også mye av skylden for hvorfor språket er så dårlig posisjonert for å dra nytte av alle resurser i moderne datamaskiner.
Dette har uten tvil vært en av de største (om ikke den største) kontroversen rundt Python, så i denne #nysnø posten vil jeg introdusere dere for Python’s Global Interpreter Lock AKA “Python’s infamous GIL”!
Bytecode
Grovt sett består Python implementasjonen av to deler: En kompilator og en “tolk” (interpreter).Når du starter et Python program blir det du har skrevet først kompilert til bytekode, som er en mellomrepresentasjon mellom det du har skrevet og maskinkode. Intensjonen er å abstrahere bort operativsystem og hardware for å sikre en konsistent oppførsel uavhengig av hvilket OS og hvilken hardware du kjører på.For å gi en liten intuisjon på hva vi snakker om, la oss ta en titt på følgende funksjon:
def add(a, b):return a + b# Kompilert til bytecode (forenklet):LOAD_FAST 0 (a)LOAD_FAST 1 (b)BINARY_ADDRETURN_VALUE
Bytekoden blir lest og utført, linje for linje, av Pythons tolk.
The GIL
GIL’en sikrer at kun én tråd i Python tolken kan kjøre bytecode om gangen, som gjør håndtering av minne veldig enkel. Python teller hvor mange prosesser som bruker et objekt. Når ingen lenger “bryr seg” om objektet, ødelegges det og minne blir frigjort:
a = []b = ac = a # Refcount for 'a' = 3del bdel adel c # Refcount for 'a' = 0, 'a' ødelegges, minne frigjort
So far so good. Men når det gjelder å utføre oppgaver i parallell, har GIL’en store konsekvenser. Ta følgende program:
import threadingdef compute_sum(start, end):# Implementasjon# Variabler her# Lag to tråder for å utføre summenthread1 = threading.Thread(target=compute_sum, args=(range_start, midpoint))thread2 = threading.Thread(target=compute_sum, args=(midpoint, range_end))# Start begge trådene, og vent til de har fullført
Hvis man ikke kjenner til GIL’en, kan man bli lurt til å tro at trådene i dette programmet kjører i parallell. Men det er ikke tilfellet. Programmet bytter raskt mellom trådene, slik at det virker som om de kjører samtidig, selv om kun én tråd faktisk kjører av gangen.
Irritasjon og Omveier
For å omgå GIL’en har Python (blant annet) laget multiprocessing biblioteket. Programmet for regne ut en sum kan skrives om slik
import multiprocessingdef compute_sum(start, end, result_queue):# Implementasjon# Variabler her# Opprett en kø for å samle resultater fra prosesseneresult_queue = multiprocessing.Queue()# Lag to prosesser for å utføre summenprocess1 = multiprocessing.Process(target=compute_sum, args=(range_start, midpoint, result_queue))process2 = multiprocessing.Process(target=compute_sum, args=(midpoint, range_end, result_queue))# Start begge prosessene og vent til de har fullført
Vi har nå oppnådd “ekte” parallellitet, ved å starte flere instanser av Python-tolkeren (en prosess) med hver sin GIL. Det kommer selvfølgelig ikke uten ulemper, relativt til hvordan tråder er implementert i språk som C++. Prosesser er generelt mer krevende enn tråder når det gjelder:
- Minnebruk
- Overhead i både oppstart og kjøretid
- Kompleksitet i kode og debugging
- Kompatibilitet på tvers av plattformer
Dette er et stort opphav til frustrasjon og irritasjon i Python miljøet. GIL’en, som originalt skulle gjøre ting enklere, har i senere tid ført til komplekse “workarounds” for å levere samme funksjonalitet som andre språk. Gitt at trenden går mot flere CPU kjerner og mer parallellitet, er ikke dette trivielt.
PEP 703
Problemene relatert til GIL’en blir tatt på alvor. PEP 703 har introdusert eksperimentell funksjonalitet som gjør at man kan bygge selve Python-tolken uten en GIL. Selv om det blir mulig å skru den av den har GIL’en en lang historisk arv. Hvis man ikke aktivt velger den bort vil den fortsatt brukes, for å sikre bakover-kompatibilitet og stabilitet.
GIL’en har naturligvis vært et tilbakevendende punkt for kritikk mot Guido van Rossum. I 2018 trakk han seg som “Benevolent Dictator For Life” (BDFL) for Python. Denne avgjørelsen var ikke direkte relatert til GIL’en, men jeg tror nok det akkumulerte stresset over flere tiår med kontrovers var en faktor.
Dette er utvilsomt en av de største endringene forslått i Python, og jeg er spent på hva utfallet blir!