Acest tutorial se va concentra pe unul dintre subiectele importante ale lui Python, GIL. Vom acoperi, de asemenea, modul în care GIL influențează performanța programelor Python cu implementarea codului. Înainte de a aborda acest subiect, să avem o idee de bază despre GIL.
GIL sau Global Interpreter Lock
Python Global Interpreter Lock sau GIL este o parte importantă a programării multithreading. Este un tip de blocare a procesului utilizat atunci când lucrați cu mai multe procese. Oferă controlul unui singur fir. În general, Python folosește un singur fir pentru a rula un singur proces. Obținem același rezultat de performanță al proceselor cu un singur și mai multe fire folosind GIL. Limitează realizarea multithreading-ului în Python, deoarece împiedică firele și funcționează ca un singur thread.
Notă - Python nu acceptă multithreading deoarece pachetele de threading nu ne-au permis să folosim mai multe nuclee CPU.
De ce dezvoltatorii Python folosesc GIL?
Python oferă caracteristica unică de contor de referință, care este utilizată pentru gestionarea memoriei. Contorul de referințe numără numărul total de referințe făcute intern în Python pentru a atribui o valoare unui obiect de date. Când numărul de referințe ajunge la zero, memoria alocată obiectului este eliberată. Să vedem exemplul de mai jos.
Exemplu -
import sys a = [] b = a sys.getrefcount(a)
Principala preocupare a variabilei de numărare de referință este că aceasta poate fi afectată atunci când două sau trei fire încearcă să-și crească sau să scadă valoarea simultan. Este cunoscut sub numele de condiție de rasă. Dacă apare această condiție, poate fi cauzată o scurgere de memorie care nu este niciodată eliberată. Este posibil să se blocheze sau să apară erori în programul Python.
GIL ne ajută să eliminăm o astfel de situație prin utilizarea blocărilor pentru toate structurile de date partajate în fire, astfel încât acestea să nu fie modificate inconsecvent. Python oferă o modalitate ușoară de a implementa GIL, deoarece se ocupă de gestionarea memoriei thread-safe. GIL necesită oferirea unei singure blocări unui fir pentru procesare în Python. Mărește performanța unui program cu un singur thread, deoarece doar o singură blocare trebuie gestionată. De asemenea, ajută la realizarea oricărui program legat de CPU și previne starea de blocaj.
Impactul asupra programelor Python cu mai multe fire
Există o diferență între limitele CPU în ceea ce privește performanța lor și legarea I/O pentru un program tipic Python sau orice program de calculator. Programele legate de CPU sunt în general împinse CPU la limitele sale. Aceste programe sunt utilizate în general pentru calcule matematice, cum ar fi înmulțirea matricei, searing, procesarea imaginilor etc.
Programele legate de I/O sunt acele programe care petrec timp pentru a obține intrări/ieșiri care pot fi generate de utilizator, fișier, bază de date, rețea etc. Astfel de programe trebuie să aștepte o perioadă semnificativă de timp până când sursa furnizează intrarea. Pe de altă parte, sursa are și propriul timp de procesare. De exemplu - un utilizator se gândește la ce să introducă ca intrare.
Să înțelegem următorul exemplu.
Exemplu -
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)
Ieșire:
Time taken in seconds - 7.422671556472778
Acum modificăm codul de mai sus rulând cele două fire.
Exemplu - 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)
Ieșire:
Time taken in seconds - 6.90830135345459
După cum putem vedea că ambele coduri au avut nevoie de același timp pentru a se termina. GIL a împiedicat firele legate de CPU să se execute în paralel în al doilea cod.
De ce nu a fost încă eliminat GIL?
Mulți programatori au o plângere în acest sens, dar Python nu poate aduce modificări la fel de semnificative precum eliminarea GIL. Un alt motiv este că GIL nu este îmbunătățit de acum. Dacă se schimbă în Python 3, va crea unele probleme serioase. În loc să elimine GIL, conceptul GIL se poate îmbunătăți. Potrivit lui Guido van Rossom -
„Aș primi un set de patch-uri în Py3k numai dacă performanța pentru un program cu un singur thread (și pentru un program cu mai multe fire, dar cu I/O) nu scade”.
Există, de asemenea, multe metode disponibile care rezolvă aceeași problemă rezolvată de GIL, dar sunt greu de implementat.
Cum să tratați GIL-ul lui Python
Utilizarea multiprocesării este cea mai potrivită modalitate de a preveni programul de la GIL. Python oferă diferiți interpreți pentru fiecare proces de rulat, astfel încât, în acel scenariu, un singur fir este furnizat fiecărui proces în multiprocesare. Să înțelegem următorul exemplu.
Exemplu -
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)
Ieșire:
Time taken in seconds - 3.3707828521728516
Poate părea că o performanță decentă este crescută, dar gestionarea proceselor are propriile cheltuieli generale și procesele multiple sunt mai grele decât firele multiple.
Concluzie
În acest tutorial, am discutat despre GIL și cum îl putem folosi. Oferă controlul unui singur fir pentru a se executa la timp. Acest tutorial a acoperit, de asemenea, de ce GIL este important pentru programatorii Python.