Tento tutoriál bude zameraný na jednu z dôležitých tém Pythonu, GIL. Tiež sa budeme zaoberať tým, ako GIL ovplyvňuje výkon programov Python s implementáciou kódu. Predtým, ako sa ponoríme do tejto témy, získajme základnú predstavu o GIL.
GIL alebo Global Interpreter Lock
Python Global Interpreter Lock alebo GIL je dôležitou súčasťou multithreadingového programovania. Je to typ procesného zámku, ktorý sa používa pri práci s viacerými procesmi. Dáva kontrolu iba jednému vláknu. Vo všeobecnosti Python používa jedno vlákno na spustenie jedného procesu. Získame rovnaký výsledok výkonu jednovláknových a viacvláknových procesov pomocou GIL. Obmedzuje dosiahnutie multithreadingu v Pythone, pretože zabraňuje vláknam a funguje ako jedno vlákno.
Poznámka - Python nepodporuje viacvláknové spracovanie, pretože balíky s vláknami nám neumožňujú používať viaceré jadrá CPU.
Prečo vývojári Pythonu používajú GIL?
Python poskytuje jedinečnú funkciu počítadla referencií, ktorá sa používa na správu pamäte. Počítadlo referencií počíta celkový počet referencií uskutočnených interne v Pythone na priradenie hodnoty k dátovému objektu. Keď počty referencií dosiahnu nulu, priradená pamäť objektu sa uvoľní. Pozrime sa na príklad nižšie.
Príklad -
import sys a = [] b = a sys.getrefcount(a)
Hlavným problémom premennej referenčného počtu je to, že môže byť ovplyvnená, keď sa dve alebo tri vlákna pokúšajú zvýšiť alebo znížiť jej hodnotu súčasne. Je známy ako rasový stav. Ak nastane tento stav, môže to byť spôsobené únikom pamäte, ktorá sa nikdy neuvoľní. Môže dôjsť k zlyhaniu alebo chybám v programe Python.
GIL nám pomáha odstrániť takúto situáciu pomocou zámkov všetkých zdieľaných dátových štruktúr naprieč vláknami, aby sa nemenili nekonzistentne. Python poskytuje jednoduchý spôsob implementácie GIL, pretože sa zaoberá správou pamäte zabezpečenou proti vláknam. GIL vyžaduje ponúkanie jediného zámku vlákna na spracovanie v Pythone. Zvyšuje výkon jednovláknového programu, pretože je potrebné obsluhovať iba jeden zámok. Pomáha tiež vytvoriť akýkoľvek program viazaný na CPU a zabraňuje zablokovaniu.
Vplyv na viacvláknové programy Python
Existuje rozdiel medzi limitmi CPU v ich výkone a I/O limitmi pre typický Python program alebo akýkoľvek počítačový program. Programy viazané na CPU vo všeobecnosti tlačia CPU na svoje limity. Tieto programy sa vo všeobecnosti používajú na matematické výpočty, ako je násobenie matíc, searing, spracovanie obrazu atď.
I/O viazané programy sú tie programy, ktoré trávia čas získavaním vstupu/výstupu, ktorý môže vygenerovať používateľ, súbor, databáza, sieť atď. Takéto programy musia čakať nejaký významný čas, kým zdroj poskytne vstup. Na druhej strane má zdroj aj svoj čas spracovania. Napríklad – používateľ rozmýšľa, čo zadať ako vstup.
Poďme pochopiť nasledujúci príklad.
Príklad -
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 start_time = time.time() countdown(COUNT) end_time = time.time() print('Time taken in seconds -', end_time - start_time)
Výkon:
Time taken in seconds - 7.422671556472778
Teraz upravíme vyššie uvedený kód spustením dvoch vlákien.
Príklad – 2:
import time from threading import Thread COUNT = 100000000 def countdown(num): while num>0: num -= 1 thread1 = Thread(target=countdown, args=(COUNT//2,)) thread2 = Thread(target=countdown, args=(COUNT//2,)) start_time = time.time() thread1.start() thread2.start() thread1.join() thread2.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
Výkon:
Time taken in seconds - 6.90830135345459
Ako vidíme, dokončenie oboch kódov trvalo rovnaký čas. GIL zabránil vláknam viazaným na CPU v paralelnom vykonávaní v druhom kóde.
Prečo GIL ešte nebol odstránený?
Mnoho programátorov má na to sťažnosť, ale Python nemôže priniesť také významné zmeny ako odstránenie GIL. Ďalším dôvodom je, že GIL zatiaľ nie je vylepšený. Ak sa to zmení v Pythone 3, spôsobí to vážne problémy. Namiesto odstránenia GIL sa môže koncept GIL zlepšiť. Podľa Guida van Rossoma -
'Privítal by som sadu záplat do Py3k iba vtedy, ak sa nezníži výkon pre jednovláknový program (a pre viacvláknový, ale I/O viazaný program).
Existuje tiež veľa dostupných metód, ktoré riešia rovnaký problém vyriešený GIL, ale je ťažké ich implementovať.
Ako sa vysporiadať s Pythonovým GIL
Použitie multiprocessingu je najvhodnejší spôsob, ako zabrániť programu GIL. Python ponúka na spustenie rôznych interpretov pre každý proces, takže v tomto scenári je každému procesu v multiprocesingu poskytnuté jediné vlákno. Poďme pochopiť nasledujúci príklad.
Príklad -
from multiprocessing import Pool import time COUNT = 50000000 def countdown(num): while num>0: num -= 1 if __name__ == '__main__': pool = Pool(processes=2) start_time = time.time() r1 = pool.apply_async(countdown, [COUNT//2]) r2 = pool.apply_async(countdown, [COUNT//2]) pool.close() pool.join() end_time = time.time() print('Time taken in seconds -', end_time - start_time)
Výkon:
Time taken in seconds - 3.3707828521728516
Môže sa zdať, že sa zvýšil slušný výkon, ale riadenie procesov má svoju vlastnú réžiu a viaceré procesy sú ťažšie ako viaceré vlákna.
Záver
V tomto návode sme diskutovali o GIL a o tom, ako ho môžeme použiť. Poskytuje kontrolu jednému vláknu, ktoré sa má spustiť v danom čase. Tento tutoriál tiež popísal, prečo je GIL dôležitý pre programátorov Pythonu.