Sobes.tech
Middle

Mis on GIL (Global Interpreter Lock) Pythonis?

sobes.tech AI

Vastus AI-lt

GIL (globaalne tõlki tõlkimise lukustus) on mehhanism CPython tõlki sees, mis lubab täita ainult ühel niidil (thread) korraga. Isegi mitmekodulistel protsessoritel ei võimalda GIL niitidel tõeliselt paralleelselt Python-koodi täita.

GIL-i olemasolu põhjused:

  • Lihtsustades mälu haldamist: GIL lihtsustab niitide ohutut mälu haldamist ja prügikoristust (viidete arvu arvestus). Ilma GIL-ita oleks vaja keerukamaid ja ressursimahukamaid lukustusmehhanisme jagatud andmestruktuuride kaitsmiseks.
  • Säilitades ühilduvuse C raamatukogudega: GIL hõlbustab olemasolevate C raamatukogudega integreerimist, mis ei pruugi olla niiditurvalised.

GIL-i mõju:

  • CPU-põhiste ülesannete paralleelsuse piiramine: Intensiivselt protsessorit kasutavate ülesannete puhul muutub GIL kitsaskohaks, sest isegi mitmekodulistel süsteemidel ei jagata täitmist protsessori tuumade vahel. Niidid peavad ootama, kuni GIL vabastatakse.
  • Vähem mõju I/O-põhistele ülesannetele: I/O-ga intensiivselt töötavate ülesannete puhul (võrgu päringud, failidega töötamine) ei ole GIL nii kriitiline. Ootamise ajal vabastab niit GIL-i, võimaldades teistel niitidel töötada.

Meetodid GIL-i ületamiseks:

  • Mitmeprotsessiline töö (Multiprocessing): Selle asemel, et kasutada niite, kasutatakse sõltumatuid protsesse. Iga protsess omab oma Python tõlki eksemplari ja seega ka oma GIL-i. Protsessid võivad töötada paralleelselt erinevatel tuumadel.
  • Asünkroonprogrammimine (asyncio): Kasutab asünkroonset sisend-väljund-mudelit ühes niidis. Selle asemel, et luua palju niite, vahetab asünkroonne kood ülesannete vahel ootamise ajal I/O operatsioonide jaoks. GIL-i mõju on sel juhul väiksem, kuna ei toimu CPU-ga seotud ülesannete paralleelset täitmist.
  • C/C++ raamatukogud: Koodiosad, mis on kirjutatud C või C++, saavad teha operatsioone ilma GIL-i hoidmata, vabastades selle täitmise ajal. Populaarsed raamatukogud, nagu NumPy ja SciPy, kasutavad seda tõhusate arvutuste jaoks.
# Näide GIL-i mõjust CPU-põhisele ülesandele
import threading
import time

def cpu_bound_task():
    count = 0
    for i in range(10**7):
        count += 1

# Kaks niiti CPU-põhise ülesande täitmiseks
t1 = threading.Thread(target=cpu_bound_task)
t2 = threading.Thread(target=cpu_bound_task)

start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()

print(f"Täitmisaeg niitidega (CPU-põhine): {end_time - start_time:.2f} sekundit") # Aeg on umbes kaks korda suurem kui ühe ülesande täitmisel, GIL-i tõttu
# Näide I/O-põhisest ülesandest, kus GIL on vähem märgatav
import threading
import time

def io_bound_task():
    time.sleep(1) # I/O ootamise imitatsioon

# Kaks niiti I/O-põhise ülesande täitmiseks
t1 = threading.Thread(target=io_bound_task)
t2 = threading.Thread(target=io_bound_task)

start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()

print(f"Täitmisaeg niitidega (I/O-põhine): {end_time - start_time:.2f} sekundit") # Aeg on umbes 1 sekund, kuna niidid vabastavad GIL-i ootamise ajal