Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. GIL nima
- 2.2. Nega bor
- 2.3. GIL qanday almashadi
- 2.4. GIL nimani kafolatlaydi va nimani yo'q
- 2.5. CPU va I/O ishi (qisqacha)
- 2.6. Erkin ipli Python (free-threading)
- 2.7. Boshqa yo'llar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — GIL ni ko'rish
- Misol 2 — GIL bo'lsa ham poyga holatlari
- Misol 3 — GIL qachon bo'shatiladi
- Misol 4 — Amaliy: iplar_foydasi() diagnostikasi
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
12.5-dars: GIL nima va nega bor
12-QISM — CPYTHON ICHKI TUZILISHI · 5-dars
1. Kirish va motivatsiya
Python haqida eng ko'p eshitiladigan gaplardan biri: "Python'da iplar (thread) foydasiz, chunki GIL bor". Ikkinchisi: "GIL bor, demak Python kodim iplar uchun xavfsiz". Ikkalasi ham noto'g'ri — va har biri real zarar keltiradi.
Birinchi vaziyat. Rasm xizmatida har yuklangan fotosurat uchun eskizlar (thumbnail) sof Python kutubxonasi bilan yasalardi. 16 yadroli serverda jamoa ishni ThreadPoolExecutor(16) ga o'tkazdi va 16 barobar tezlashishni kutdi. Natija: 0.9 barobar — biroz sekinroq. Shu vaqtning o'zida boshqa jamoa xuddi shu iplar bilan S3 dan fayllarni yuklab olishni 12 barobar tezlashtirdi.
Ikkinchi vaziyat. Bonus ballari servisi: hisob.ball = yangi_ball(hisob.ball). "GIL bor, poyga holati bo'lmaydi" deb qulf qo'yilmadi. Sinovlarda hammasi to'g'ri edi. Production'da, yuk ostida, har kuni bir necha yuz ball yo'qoldi.
Ikkala vaziyatni ham bitta mexanizm tushuntiradi — Global Interpreter Lock.
Bu darsda:
- GIL nima: bir interpretatorda bir vaqtda bitta ip Python bayt-kodini bajaradi
- Nega bor: havolalar sanog'i, ichki tuzilmalar, C kengaytmalar soddaligi
- GIL qanday almashadi:
switch interval, "eval breaker", bloklovchi chaqiruvlar - GIL nimani kafolatlaydi va nimani kafolatlamaydi
- GIL qachon bo'shatiladi: I/O,
time.sleep,hashlib,zlib, NumPy - Erkin ipli Python (PEP 703, 3.13+; 3.14 da rasman qo'llab-quvvatlanadi)
- Subinterpretatorlar va o'z GIL i (PEP 684, 734)
- Amaliy: funksiya iplardan foyda ko'radimi — diagnostika vositasi
2. Nazariya — chuqur tushuntirish
2.1. GIL nima
GIL — CPython interpretatori ichidagi bitta qulf (mutex). Qoidasi:
Bitta interpretatorda bir vaqtning o'zida faqat bitta ip Python bayt-kodini bajarishi yoki Python obyektlariga C API orqali murojaat qilishi mumkin.
Yadro 1: ip A ████████░░░░░░░░████████░░░░░░░░
Yadro 2: ip B ░░░░░░░░████████░░░░░░░░████████
← GIL navbat bilan uzatiladi →- Iplar — haqiqiy operatsion tizim iplari, har biri alohida yadroda bo'lishi mumkin
- Lekin Python kodini bajarish uchun GIL kerak — shuning uchun sof Python hisoblash iplarda parallel emas, navbatma-navbat
- I/O kutayotgan ip GIL ni bo'shatadi — kutish vaqtida boshqalar ishlaydi
2.2. Nega bor
1. Havolalar sanog'i (11.4-dars). Har murojaatda ob_refcnt o'zgaradi:
obj->ob_refcnt++; /* aslida: o'qi → qo'sh → yoz (3 mashina amali) */Ikki ip bir obyektning sanog'ini bir vaqtda oshirsa, bitta oshirish yo'qolishi mumkin → obyekt muddatidan oldin o'chadi → yiqilish. Har ++ ni atom amal (atomic) yoki qulf bilan qilish — bitta ipli kodni sezilarli sekinlashtiradi. Bitta katta qulf — eng arzon yechim.
2. Ichki tuzilmalar. dict, list, xotira ajratuvchi (pymalloc), GC, import tizimi, interning jadvali — hammasi GIL himoyasida qulfsiz yozilgan.
3. C kengaytmalar. C API da kengaytma muallifi GIL ni ushlab turib, har qanday Python obyekti bilan xavfsiz ishlay oladi. Python'ning ulkan ekotizimi (NumPy, lxml, psycopg) aynan shu soddalik tufayli yaratilgan.
4. Tarix. GIL 1992 yilda iplar qo'llab-quvvatlanishi qo'shilganda paydo bo'lgan — o'shanda ko'p yadroli kompyuterlar deyarli yo'q edi, bitta ipli tezlik muhimroq edi.
| Afzallik | Narx |
|---|---|
| Bitta ipli kod tez | Sof Python hisoblash iplarda parallel emas |
| Interpretator ichki xavfsizligi | Ko'p yadrodan foydalanish uchun jarayonlar kerak |
| C kengaytmalar yozish oson | Erkin ipli rejimga o'tish katta ish (2.6) |
2.3. GIL qanday almashadi
ip A GIL ushlab ishlayapti
ip B GIL kutmoqda ──── 5 ms (switch interval) o'tdi ────► "bo'shat" so'rovi
ip A keyingi tekshiruv nuqtasida GIL ni bo'shatadi
ip B GIL ni oladi| Tushuncha | Tafsilot |
|---|---|
sys.getswitchinterval() |
Sukut 0.005 soniya — kutayotgan ip shuncha kutgach so'rov yuboradi |
| "Eval breaker" | Interpretator so'rovni har instruksiyada emas, ma'lum nuqtalarda tekshiradi — funksiyaga kirish (RESUME), sikl orqaga sakrashi (JUMP_BACKWARD) va boshqalar |
| Bloklovchi chaqiruvlar | read, recv, sleep, select, lock.acquire — C kodi GIL ni o'zi bo'shatadi (Py_BEGIN_ALLOW_THREADS) |
| C kengaytmalar | Uzoq hisob-kitob davomida GIL ni bo'shatishi mumkin: hashlib (katta ma'lumotda), zlib, bz2, NumPy ning ko'p amallari |
"Iplar faqat instruksiyalar orasida almashadi" — to'g'ri, lekin qaysi instruksiyalar orasida — implementatsiya tafsiloti. Kodning to'g'riligi bunga tayanmasligi kerak.
2.4. GIL nimani kafolatlaydi va nimani yo'q
| Kafolatlaydi | Kafolatlamaydi |
|---|---|
| Interpretator ichki tuzilmalari buzilmaydi (yiqilish yo'q) | Sizning obyektlaringiz invariantlari |
Bitta ichki C amali (list.append, dict[k] = v) amalda atomar |
"O'qi → hisobla → yoz" ketma-ketligi |
| Havolalar sanog'i to'g'ri | "Tekshir → bajar" (if k not in d: d[k] = ...) |
| Bir nechta atribut/lug'at yozuvi izchilligi | |
| Erkin ipli yig'mada va boshqa implementatsiyalarda (PyPy, GraalPy) ichki amallar atomarligi ham |
hisob.ball = yangi_ball(hisob.ball)
# ip A: o'qidi 100 ip B: o'qidi 100
# ip A: yozdi 101 ip B: yozdi 101 ← bitta oshirish yo'qoldiyangi_ball() funksiya chaqiruvi — GIL almashishi mumkin bo'lgan nuqta. Sukut bo'yicha 5 ms intervalda bu kamdan-kam uchraydi — va aynan shuning uchun xavfli: sinovlarda ko'rinmaydi, yuk ostida chiqadi.
Yechim: threading.Lock, navbatlar (queue.Queue), o'zgarmas ma'lumot, bitta ipda o'zgartirish (14-qism).
2.5. CPU va I/O ishi (qisqacha)
| Ish turi | GIL ta'siri | Iplar |
|---|---|---|
I/O: tarmoq, disk, baza, sleep |
Kutishda GIL bo'shatiladi | Samarali |
C kutubxonasi GIL ni bo'shatadi (hashlib, NumPy) |
Hisob GIL siz | Samarali |
Sof Python hisoblash (sikllar, parsing, json, re) |
GIL ushlanadi | Parallel emas, hatto sekinroq |
Batafsil o'lchovlar va strategiya tanlash — 12.6-darsda.
2.6. Erkin ipli Python (free-threading)
| Versiya | Holat |
|---|---|
| 3.13 | PEP 703: python3.13t — tajribaviy, alohida yig'ma |
| 3.14 | PEP 779: rasman qo'llab-quvvatlanadi, lekin hali ham alohida yig'ma (python3.14t), sukut emas |
| Keyinroq | Sukutga aylantirish — ekotizim tayyorligiga qarab |
GIL siz havolalar sanog'i va tuzilmalar qanday himoyalanadi:
| Mexanizm | Nima qiladi |
|---|---|
| Biased reference counting | Obyekt yaratgan ip sanoqni tez (lokal) o'zgartiradi, boshqa iplar — atom amal bilan (umumiy) |
| Immortal obyektlar (11.1, 12.4) | None, kichik sonlar va h.k. sanog'i umuman o'zgarmaydi |
| Kechiktirilgan sanoq | Funksiya, kod, modul kabi tez-tez murojaat qilinadigan obyektlar uchun |
| Obyekt darajasidagi qulflar | list, dict ichki kichik qulflar bilan |
| mimalloc | Iplar uchun xavfsiz xotira ajratuvchi |
Narxi va shartlari:
- Bitta ipli kod erkin ipli yig'mada biroz sekinroq (3.14 da taxminan 5–10%)
- C kengaytmalar erkin ipli rejimni qo'llab-quvvatlashini e'lon qilishi kerak (
Py_mod_gil); aks holda import paytida GIL qayta yoqiladi - Tekshirish:
sys._is_gil_enabled(),sysconfig.get_config_var("Py_GIL_DISABLED"), boshqarish:PYTHON_GIL=0yoki-X gil=0 - Poyga holatlari 2.4-bob erkin ipli rejimda ko'proq uchraydi — qulflar hali ham kerak
2.7. Boshqa yo'llar
| Vosita | GIL | Qachon |
|---|---|---|
multiprocessing, ProcessPoolExecutor |
Har jarayonda o'zi | CPU ish, ma'lumot almashish qimmat (pickle) |
| Subinterpretatorlar (PEP 684, 3.12+) | Har interpretatorda o'z GIL i | Bitta jarayonda parallellik |
concurrent.interpreters, InterpreterPoolExecutor (PEP 734, 3.14) |
Har biri o'zi | Subinterpretatorlar uchun Python API |
asyncio |
Bitta ip | Juda ko'p I/O ulanishlar (14-qism) |
| C/Rust kengaytma GIL ni bo'shatib | Hisob GIL siz | Issiq kod (30-qism) |
3. Tez ma'lumotnoma
Holatni ko'rish
sys.getswitchinterval() → 0.005 sys.setswitchinterval(s)
sys._is_gil_enabled() → True/False
sysconfig.get_config_var("Py_GIL_DISABLED") → 1 erkin ipli yig'mada
PYTHON_GIL=0 / -X gil=0 (faqat erkin ipli yig'mada)Qoidalar
bir interpretator — bir vaqtda bitta ip Python kodini bajaradi
I/O, sleep, lock kutish, hashlib/zlib/NumPy → GIL bo'shatiladi
sof Python CPU ish → iplarda parallel emas
GIL ≠ sizning kodingiz uchun xavfsizlik → LockMuqobillar
CPU: ProcessPoolExecutor, InterpreterPoolExecutor (3.14), C kengaytma, python3.14t
I/O: ThreadPoolExecutor, asyncio4. Batafsil misollar
Misol 1 — GIL ni ko'rish
"""Yig'ma ma'lumoti; sof Python CPU ish 1 va 4 ipda; sleep 4 ipda; eval breaker: almashish qayerda sodir bo'ladi."""
import sys
import sysconfig
import threading
import time
def iplarda(funksiya, iplar, *args):
royxat = [threading.Thread(target=funksiya, args=args) for _ in range(iplar)]
bosh = time.perf_counter()
for ip in royxat:
ip.start()
for ip in royxat:
ip.join()
return time.perf_counter() - bosh
def eng_yaxshi(funksiya, iplar, *args, takror=3):
return min(iplarda(funksiya, iplar, *args) for _ in range(takror))
print("=== 1. Yig'ma ===")
print(f" versiya: {sys.version_info[:2]}, erkin ipli yig'ma: {bool(sysconfig.get_config_var('Py_GIL_DISABLED'))}")
print(f" GIL yoqilgan: {sys._is_gil_enabled()}, switch interval: {sys.getswitchinterval()} s")
def hisobla(n=2_000_000):
s = 0
for i in range(n):
s += i * i
return s
print("\n=== 2. Sof Python hisoblash ===")
bitta = eng_yaxshi(hisobla, 1)
tort = eng_yaxshi(hisobla, 4)
print(" 4 ip 4 barobar ish qildi; parallel bo'lganda vaqt 1 ipnikiga teng bo'lardi")
print(f" haqiqatda kamida 3 barobar uzoqroq (ketma-ket kabi yoki yomonroq): {tort / bitta >= 3}")
def kut():
time.sleep(0.2)
print("\n=== 3. Kutish (I/O ga o'xshash) ===")
vaqt = eng_yaxshi(kut, 8)
print(f" 8 ip, har biri 0.2 s uxlaydi: jami {vaqt:.2f} s — parallel: {vaqt < 0.4}")
print("\n=== 4. Almashish nuqtalari ===")
navbat = []
def ishchi(nom, bosqichlar):
for _ in range(bosqichlar):
hisobla(300_000)
navbat.append(nom)
iplarda_ = [threading.Thread(target=ishchi, args=(nom, 6)) for nom in "AB"]
for ip in iplarda_:
ip.start()
for ip in iplarda_:
ip.join()
almashishlar = sum(1 for a, b in zip(navbat, navbat[1:]) if a != b)
print(f" 12 bosqich, ikki ip: ular aralashib ishladi (kamida bitta almashish): {almashishlar >= 1}")
print(" lekin bir vaqtda emas — GIL navbat bilan uzatildi (2-bo'lim)")Natijaning muhim qismi:
=== 1. Yig'ma ===
versiya: (3, 14), erkin ipli yig'ma: False
GIL yoqilgan: True, switch interval: 0.005 s
=== 2. Sof Python hisoblash ===
4 ip 4 barobar ish qildi; parallel bo'lganda vaqt 1 ipnikiga teng bo'lardi
haqiqatda kamida 3 barobar uzoqroq (ketma-ket kabi yoki yomonroq): True
=== 3. Kutish (I/O ga o'xshash) ===
8 ip, har biri 0.2 s uxlaydi: jami 0.20 s — parallel: True
=== 4. Almashish nuqtalari ===
12 bosqich, ikki ip: ular aralashib ishladi (kamida bitta almashish): True
lekin bir vaqtda emas — GIL navbat bilan uzatildi (2-bo'lim)Nima ko'rsatdi: 2.1, 2.3, 2.5-bo'limlar. 4 ipli sof Python ish nafaqat tezlashmadi, balki GIL uchun kurash tufayli ketma-ket bajarilgandan ham sekinroq bo'ldi.
Misol 2 — GIL bo'lsa ham poyga holatlari
"""O'qi-hisobla-yoz: switch interval bilan yo'qotishlar; list.append atomar; tekshir-bajar poygasi; Lock bilan tuzatish."""
import sys
import threading
import time
def iplarda(funksiya, iplar=4):
royxat = [threading.Thread(target=funksiya) for _ in range(iplar)]
for ip in royxat:
ip.start()
for ip in royxat:
ip.join()
class Hisob:
def __init__(self):
self.ball = 0
def yangi_ball(eski):
return eski + 1
N = 100_000
print("=== 1. hisob.ball = yangi_ball(hisob.ball) ===")
sys.setswitchinterval(0.000001)
hisob = Hisob()
def oshir():
for _ in range(N):
hisob.ball = yangi_ball(hisob.ball)
iplarda(oshir)
print(f" kutilgan {4 * N:,}, oshirishlar yo'qoldi: {hisob.ball < 4 * N}, yo'qotish 1% dan ko'p: {hisob.ball < 0.99 * 4 * N}")
print(" (switch interval 1 mks — sukut 5 ms da xuddi shu xato kamdan-kam, lekin baribir bo'ladi)")
qulf = threading.Lock()
hisob = Hisob()
def qulf_bilan():
for _ in range(N):
with qulf:
hisob.ball = yangi_ball(hisob.ball)
iplarda(qulf_bilan)
print(f" Lock bilan: {hisob.ball:,} — to'g'ri: {hisob.ball == 4 * N}")
print("\n=== 2. Bitta ichki amal: list.append ===")
royxat = []
def qosh():
for i in range(N):
royxat.append(i)
iplarda(qosh)
print(f" 4 × {N:,} append: {len(royxat):,} — yo'qotish yo'q: {len(royxat) == 4 * N}")
print("\n=== 3. Tekshir → bajar ===")
yaratilgan = []
class Ulanish:
def __init__(self, nom):
time.sleep(0.001) # tarmoq ulanishi simulyatsiyasi
yaratilgan.append(nom)
kesh = {}
def ulanish_ol(nom):
if nom not in kesh:
ulanish = Ulanish(nom)
kesh[nom] = ulanish
return kesh[nom]
def mijoz():
for i in range(2_000):
ulanish_ol(f"db-{i % 50}")
iplarda(mijoz, iplar=8)
print(f" 50 xil nom uchun ortiqcha ulanishlar yaratildi: {len(yaratilgan) > 50}")
yaratilgan.clear()
kesh.clear()
kesh_qulfi = threading.Lock()
def ulanish_ol_xavfsiz(nom):
with kesh_qulfi:
if nom not in kesh:
kesh[nom] = Ulanish(nom)
return kesh[nom]
def mijoz_xavfsiz():
for i in range(2_000):
ulanish_ol_xavfsiz(f"db-{i % 50}")
iplarda(mijoz_xavfsiz, iplar=8)
print(f" qulf bilan: {len(yaratilgan)} ta ulanish — to'g'ri: {len(yaratilgan) == 50}")
sys.setswitchinterval(0.005)Natijaning muhim qismi:
=== 1. hisob.ball = yangi_ball(hisob.ball) ===
kutilgan 400,000, oshirishlar yo'qoldi: True, yo'qotish 1% dan ko'p: True
(switch interval 1 mks — sukut 5 ms da xuddi shu xato kamdan-kam, lekin baribir bo'ladi)
Lock bilan: 400,000 — to'g'ri: True
=== 2. Bitta ichki amal: list.append ===
4 × 100,000 append: 400,000 — yo'qotish yo'q: True
=== 3. Tekshir → bajar ===
50 xil nom uchun ortiqcha ulanishlar yaratildi: True
qulf bilan: 50 ta ulanish — to'g'ri: TrueNima ko'rsatdi: 2.4-bo'lim. GIL interpretatorni himoya qildi (list.append yo'qotmadi), lekin bizning ikki qadamli mantiqimizni — yo'q.
Misol 3 — GIL qachon bo'shatiladi
"""hashlib katta va kichik ma'lumotda; zlib; sof Python va C kutubxonasi solishtiruvi; iplar tezlanishi nisbati."""
import hashlib
import os
import threading
import time
import zlib
KATTA = os.urandom(40_000_000)
KICHIK = [os.urandom(64) for _ in range(200_000)]
def iplarda(funksiya, iplar):
royxat = [threading.Thread(target=funksiya) for _ in range(iplar)]
bosh = time.perf_counter()
for ip in royxat:
ip.start()
for ip in royxat:
ip.join()
return time.perf_counter() - bosh
def tezlanish(funksiya, iplar=4, takror=5):
bitta = min(iplarda(funksiya, 1) for _ in range(takror))
kop = min(iplarda(funksiya, iplar) for _ in range(takror))
return iplar * bitta / kop
def katta_xesh():
hashlib.sha256(KATTA).digest()
def kichik_xeshlar():
for bolak in KICHIK:
hashlib.sha256(bolak).digest()
def siqish():
zlib.compress(KATTA[:8_000_000], 6)
def sof_python_xesh():
h = 0
for b in KATTA[:3_000_000]:
h = (h * 31 + b) & 0xFFFFFFFF
print("=== 4 ip, 4 barobar ish: bir xil sharoitda o'lchangan tezlanish ===")
t_katta = tezlanish(katta_xesh)
t_kichik = tezlanish(kichik_xeshlar)
t_sof = tezlanish(sof_python_xesh)
print(f" eng yaxshi tezlangan ish: {max([('sha256(40 MB)', t_katta), ('sha256 × 200 000 kichik', t_kichik), ('sof Python sikl', t_sof)], key=lambda j: j[1])[0]}")
print(f" sha256(40 MB) kichik bo'laklardan kamida 2 barobar yaxshi tezlandi: {t_katta > 2 * t_kichik}")
print(f" sha256(40 MB) sof Python sikldan kamida 2 barobar yaxshi tezlandi: {t_katta > 2 * t_sof}")
print(f" kichik bo'laklar sof Python sikliga yaqin (GIL ushlanadi): {t_kichik < 2 * t_sof}")
print("\n hashlib GIL ni faqat katta ma'lumotda bo'shatadi; kichik bo'laklarda chaqiruvlar orasidagi Python kodi GIL talab qiladi")
Natijaning muhim qismi:
=== 4 ip, 4 barobar ish: bir xil sharoitda o'lchangan tezlanish ===
eng yaxshi tezlangan ish: sha256(40 MB)
sha256(40 MB) kichik bo'laklardan kamida 2 barobar yaxshi tezlandi: True
sha256(40 MB) sof Python sikldan kamida 2 barobar yaxshi tezlandi: True
kichik bo'laklar sof Python sikliga yaqin (GIL ushlanadi): True
hashlib GIL ni faqat katta ma'lumotda bo'shatadi; kichik bo'laklarda chaqiruvlar orasidagi Python kodi GIL talab qiladiNima ko'rsatdi: 2.3, 2.5-bo'limlar. Bitta katta C chaqiruvi GIL siz ishladi; xuddi shu kutubxona ko'p kichik chaqiruvlarda esa foyda bermadi.
Misol 4 — Amaliy: iplar_foydasi() diagnostikasi
Kirishdagi rasm xizmati vaziyatining oldini olish uchun vosita: funksiyani 1 va N ipda o'lchab, "iplar yordam beradimi" degan savolga javob beradi va tavsiya chiqaradi. Bir nechta real ish turlarida sinaymiz.
"""Funksiyani 1 va N ipda o'lchash; samarali tezlanish; tasnif va tavsiya; I/O, C kutubxona, sof Python, json va re."""
import hashlib
import json
import os
import re
import sys
import threading
import time
from dataclasses import dataclass
@dataclass
class Tashxis:
nom: str
tezlanish: float
asos: float # ataylab GIL ushlaydigan ish uchun o'lchangan tezlanish
@property
def tasnif(self):
"""Mutlaq chegara emas, xuddi shu mashinada o'lchangan asosga nisbatan."""
if self.tezlanish >= 2.5 * self.asos:
return "parallel"
if self.tezlanish <= 2.0 * self.asos:
return "GIL bilan cheklangan"
return "qisman"
@property
def tavsiya(self):
return {
"parallel": "ThreadPoolExecutor yetarli",
"GIL bilan cheklangan": "ProcessPoolExecutor / InterpreterPoolExecutor yoki C kengaytma",
"qisman": "o'lchab tanlang: iplar va jarayonlar",
}[self.tasnif]
def iplar_foydasi(nom, funksiya, asos=1.0, iplar=4, takror=5):
def olch(k):
eng = float("inf")
for _ in range(takror):
royxat = [threading.Thread(target=funksiya) for _ in range(k)]
bosh = time.perf_counter()
for ip in royxat:
ip.start()
for ip in royxat:
ip.join()
eng = min(eng, time.perf_counter() - bosh)
return eng
return Tashxis(nom, iplar * olch(1) / olch(iplar), asos)
MALUMOT = [{"id": i, "ism": f"talaba{i}", "baholar": list(range(i % 10))} for i in range(20_000)]
MATN = " ".join(f"user{i}@wisar.uz tel:+99890{i:07d}" for i in range(20_000))
RASM = os.urandom(30_000_000)
def tarmoq_sorovi():
time.sleep(0.15)
def rasm_xeshi():
hashlib.blake2b(RASM).digest()
def eskiz_sof_python():
piksellar = RASM[:600_000]
return bytes((piksellar[i] + piksellar[i + 1]) // 2 for i in range(0, len(piksellar) - 1, 2))
def json_serializatsiya():
json.dumps(MALUMOT)
def regex_qidiruv():
re.findall(r"[\w.]+@[\w.]+", MATN)
print(f"=== Diagnostika (GIL: {sys._is_gil_enabled()}) ===")
ishlar = [("tarmoq so'rovi (sleep)", tarmoq_sorovi), ("blake2b(30 MB)", rasm_xeshi),
("eskiz, sof Python", eskiz_sof_python), ("json.dumps", json_serializatsiya), ("re.findall", regex_qidiruv)]
asos = iplar_foydasi("asos", eskiz_sof_python).tezlanish # sof Python — ataylab GIL bilan cheklangan
print(f" asos (sof Python ish) tezlanishi 1 ga yaqin: {asos < 2}\n")
for nom, funksiya in ishlar:
t = iplar_foydasi(nom, funksiya, asos=asos)
print(f" {t.nom:24} → {t.tasnif:22} {t.tavsiya}")Natijaning muhim qismi:
=== Diagnostika (GIL: True) ===
asos (sof Python ish) tezlanishi 1 ga yaqin: True
tarmoq so'rovi (sleep) → parallel ThreadPoolExecutor yetarli
blake2b(30 MB) → parallel ThreadPoolExecutor yetarli
eskiz, sof Python → GIL bilan cheklangan ProcessPoolExecutor / InterpreterPoolExecutor yoki C kengaytma
json.dumps → GIL bilan cheklangan ProcessPoolExecutor / InterpreterPoolExecutor yoki C kengaytma
re.findall → GIL bilan cheklangan ProcessPoolExecutor / InterpreterPoolExecutor yoki C kengaytmaNima ko'rsatdi: 2.3–2.5, 2.7-bo'limlar — json va re C tilida yozilgan bo'lsa ham GIL ni bo'shatmaydi (Python obyektlari bilan ishlaydi), shuning uchun "C da yozilgan = parallel" degan faraz ham noto'g'ri. Tavsiya etilgan strategiyalar 12.6-darsda o'lchanadi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Python iplari haqiqiy iplar emas" | Haqiqiy OT iplari; faqat Python kodi navbatma-navbat bajariladi |
| "Python'da iplar foydasiz" | I/O va GIL ni bo'shatadigan C kodi uchun juda foydali |
| "GIL bor — kodim iplar uchun xavfsiz" | Ichki tuzilmalar xavfsiz, sizning ko'p qadamli mantiqingiz emas |
| "Iplar har instruksiyadan keyin almashadi" | Faqat ma'lum tekshiruv nuqtalarida — lekin bunga tayanmang |
| "C da yozilgan kutubxona GIL ni bo'shatadi" | Faqat Python obyektlarisiz ishlaganda (hashlib ha, json yo'q) |
| "GIL — Python tilining qismi" | CPython implementatsiyasi; Jython, IronPython da yo'q |
| "3.14 da GIL olib tashlandi" | Erkin ipli alohida yig'ma rasman qo'llab-quvvatlanadi; sukut — GIL li |
| "Erkin ipli Python'da qulflar kerak emas" | Aksincha — poyga holatlari tezroq namoyon bo'ladi |
6. Keng tarqalgan xatolar va yechimlari
1. CPU ishni iplarga berish
with ThreadPoolExecutor(16) as ex:
list(ex.map(eskiz_yasa, rasmlar)) # ❌ sof Python — tezlashmaydi
with ProcessPoolExecutor() as ex:
list(ex.map(eskiz_yasa, rasmlar)) # ✅ (12.6-dars)2. GIL ga tayanib qulf qo'ymaslik
self.ball = self.hisobla(self.ball) # ❌ ko'p ipda
with self._qulf:
self.ball = self.hisobla(self.ball) # ✅3. "Tekshir → bajar" keshi
if kalit not in kesh:
kesh[kalit] = qimmat_yasash() # ❌ bir necha ip bir vaqtda yasaydi Qulf yoki functools.cache bilan o'ralgan funksiya (ichki qulflari bor), yoki dict.setdefault (lekin qiymat baribir oldin yasaladi).
4. Poyga xatosini sys.setswitchinterval bilan "tuzatish"
sys.setswitchinterval(1) # ❌ ehtimollikni kamaytiradi, xatoni emas5. Sinovda xato ko'rinmagani uchun xavfsiz deb hisoblash
# test: 2 ip, 1000 iteratsiya — hammasi to'g'ri ❌ production: 32 ip, soatlab yuk Stress-testlar kichik switchinterval bilan (Misol 2) va erkin ipli yig'mada.
6. GIL ni bo'shatadigan kutubxonada ham ma'lumotni mayda bo'laklarga bo'lish
for bolak in bolaklar_64_bayt:
hashlib.sha256(bolak) # ⚠️ GIL deyarli bo'shatilmaydi
hashlib.sha256(b"".join(bolaklar)) # ✅ bitta katta chaqiruv (agar mantiqqa to'g'ri kelsa)7. Erkin ipli yig'mada C kengaytmalar holatini tekshirmaslik
python3.14t app.py # ⚠️ bitta eski kengaytma GIL ni qayta yoqadi
python3.14t -c "import sys, app; print(sys._is_gil_enabled())" # ✅ tekshiruv8. Iplarda print natijalari tartibiga tayanish
# ❌ chiqish tartibi har ishga tushishda boshqacha — test assertlari tartibga bog'lanmasin7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 11.4-dars (o'tilgan): havolalar sanog'i — GIL ning asosiy sababi
- 12.2-dars (o'tilgan):
+=bir necha instruksiya - 12.4-dars (o'tilgan):
ob_refcnt, immortal obyektlar, erkin ipli sarlavha - 12.6-dars: GIL qachon muammo — iplar, jarayonlar, subinterpretatorlar,
asyncioo'lchovlari - 14-qism:
threading, qulflar, navbatlar,asyncio,multiprocessing - 24-qism: NumPy — GIL ni bo'shatuvchi amallar
- 29-qism: ko'p yadroli serverlarda miqyoslash
- 30-qism: C/Rust kengaytmalarda GIL ni bo'shatish
8. Eng yaxshi amaliyotlar
Ish turini aniqlang: I/O, GIL ni bo'shatuvchi C yoki sof Python CPU — o'lchab (Misol 4).
Umumiy o'zgaruvchan holatga har doim qulf yoki navbat.
I/O uchun iplar yoki
asyncio, CPU uchun jarayonlar yoki subinterpretatorlar.Og'ir hisobni katta C chaqiruvlarida bajaring (NumPy,
hashlibbitta katta bufer).Poyga holatlarini kichik
switchintervalbilan stress-test qiling.Kutubxonalaringizni
python3.14tda sinab ko'ring.GIL ga tayanadigan "atomarlik" farazlarini hujjatlashtirmang va ishlatmang.
Ish vaqtida
sys._is_gil_enabled()ni log qiling — qaysi rejimda ishlayotganingizni biling.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import sys
import threading
1. print(sys.getswitchinterval())
2. print(sys._is_gil_enabled())
3. print(threading.active_count())
4. print(threading.current_thread().name)
5. natija = []
ip = threading.Thread(target=lambda: natija.append(threading.current_thread().name), name="ishchi-1")
ip.start()
ip.join()
print(natija)
6. royxat = []
iplar = [threading.Thread(target=lambda: royxat.extend(range(1000))) for _ in range(4)]
for t in iplar:
t.start()
for t in iplar:
t.join()
print(len(royxat))
7. sys.setswitchinterval(0.01)
print(sys.getswitchinterval())
8. sys.setswitchinterval(0.005)
q = threading.Lock()
print(q.acquire(blocking=False), q.acquire(blocking=False))
9. q.release()
print(q.locked())
10. r = threading.RLock()
with r:
with r:
print("RLock qayta kirildi")
11. ip = threading.Thread(target=lambda: None, daemon=True)
print(ip.daemon, ip.is_alive())
12. import sysconfig
print(sysconfig.get_config_var("Py_GIL_DISABLED"))Javoblar
0.005True— oddiy (GIL li) yig'mada1— faqat asosiy ipMainThread['ishchi-1']4000—list.extendbitta ichki amal0.01True False— oddiyLockqayta kirishga ruxsat bermaydiFalseRLock qayta kirildiTrue False— hali ishga tushirilmagan0— GIL li yig'mada
Vazifa 2: Xatolarni tuzating
1. class Hisoblagich:
def __init__(self):
self.qiymat = 0
def oshir(self):
joriy = self.qiymat
self.qiymat = joriy + 1
2. with ThreadPoolExecutor(max_workers=os.cpu_count()) as ex:
natijalar = list(ex.map(tub_sonlar_soni, oraliqlar))
3. _nusxa = None
def sozlama():
global _nusxa
if _nusxa is None:
_nusxa = Sozlama.fayldan_yukla()
return _nusxa
4. def xeshla(fayllar):
with ThreadPoolExecutor(8) as ex:
return list(ex.map(lambda yol: hashlib.sha256(open(yol, "rb").read()).hexdigest(), fayllar))
5. def test_hisoblagich():
h = Hisoblagich()
iplar = [threading.Thread(target=h.oshir) for _ in range(2)]
...
assert h.qiymat == 2 # "o'tdi — demak xavfsiz"Javoblar
1. class Hisoblagich:
def __init__(self):
self.qiymat = 0
self._qulf = threading.Lock()
def oshir(self):
with self._qulf:
self.qiymat += 1
2. with ProcessPoolExecutor() as ex: # sof Python CPU ish
natijalar = list(ex.map(tub_sonlar_soni, oraliqlar))
3. _nusxa = None
_qulf = threading.Lock()
def sozlama():
global _nusxa
if _nusxa is None:
with _qulf:
if _nusxa is None: # ikki marta tekshirish
_nusxa = Sozlama.fayldan_yukla()
return _nusxa
# yoki: @functools.cache def sozlama(): ...
4. # Kod to'g'ri va samarali: fayl o'qish va katta sha256 GIL ni bo'shatadi.
# Faqat faylni yopish kerak:
def bitta(yol):
with open(yol, "rb") as f:
return hashlib.file_digest(f, "sha256").hexdigest()
def xeshla(fayllar):
with ThreadPoolExecutor(8) as ex:
return list(ex.map(bitta, fayllar))
5. def test_hisoblagich():
old = sys.getswitchinterval()
sys.setswitchinterval(1e-6) # almashishni ko'paytirish
try:
h = Hisoblagich()
iplar = [threading.Thread(target=lambda: [h.oshir() for _ in range(50_000)]) for _ in range(8)]
for t in iplar: t.start()
for t in iplar: t.join()
assert h.qiymat == 400_000
finally:
sys.setswitchinterval(old)Vazifa 3: Almashish vizualizatori
- Ikki-to'rt ipli sof Python ishini ishga tushiring; har ip har 10 ms da
time.perf_counter()bilan "men ishlayapman" yozuvini qoldirsin - Yozuvlardan vaqt chizig'ini (ASCII) chizing: har ustun — 5 ms, har qator — ip
sys.setswitchintervalni 0.001, 0.005, 0.05 qilib, chiziq qanday o'zgarishini ko'rsating- Bitta ip
time.sleepbilan I/O simulyatsiya qilganda chiziq qanday bo'lishini tushuntiring
Vazifa 4: Poyga holatlari kolleksiyasi
Har biri uchun buzilgan va tuzatilgan versiya, hamda buzilishni ishonchli ko'rsatadigan stress-test yozing:
- O'qi-hisobla-yoz (hisoblagich)
- Tekshir-bajar (dangasa singleton)
- Ikki tuzilmani izchil yangilash (bank: bir hisobdan ayirib, boshqasiga qo'shish, jami o'zgarmasligi)
- Iterator bilan ro'yxatni aylanish, boshqa ip esa o'zgartiradi
- Natijalarni jadvalda: 100 ishga tushirishda nechtasida xato chiqdi
Vazifa 5: GIL ni bo'shatuvchilarni topish
Standart kutubxonadan quyidagilarni Misol 4 dagi vosita bilan tekshiring va jadval tuzing:
hashlib(turli hajmlar),zlib,bz2,lzmajson.dumps/loads,pickle,resorted(katta ro'yxat),str.join- Fayl o'qish/yozish,
socket(mahalliy server bilan) - Natijalarni CPython manba kodidagi
Py_BEGIN_ALLOW_THREADSqidiruvi bilan tasdiqlang
Vazifa 6: Erkin ipli yig'ma
python3.14t ni o'rnating (python.org o'rnatuvchisidagi "free-threaded" belgisi yoki uv python install 3.14t) va:
- Misol 1, 3 va 4 ni ikkala yig'mada ishga tushiring
- Sof Python CPU ishning 4 ipdagi tezlanishini solishtiring
- Bitta ipli tezlik farqini
timeitbilan o'lchang - Misol 2 dagi poyga holatlari qanday o'zgarishini kuzating
- O'zingiz ishlatadigan 5 ta kutubxonani import qilib,
sys._is_gil_enabled()ni tekshiring
Vazifa 7: O'ylash
Ruby (CRuby) da ham GVL (Global VM Lock) bor va Ruby 3 da "Ractor" lar qo'shildi; JavaScript'da umumiy xotirali iplar o'rniga "Web Workers" va xabar almashish tanlangan; Java va Go GIL siz, lekin sinxronizatsiya dasturchi zimmasida; PyPy'da ham GIL bor. Nega aynan dinamik, havolalar sanog'i yoki sodda C API ga ega tillar global qulfni tanlagan, va Python'ning bir vaqtning o'zida uch yo'ldan (erkin ipli yig'ma, subinterpretatorlar, jarayonlar) borishi to'g'ri strategiyami?
Javob
Qisqa javob: global qulf — dinamik til interpretatorini iplar uchun xavfsiz qilishning eng arzon yo'li: har bir ichki tuzilmani alohida himoyalash o'rniga bitta qulf. U bitta ipli tezlikni va C kengaytmalar soddaligini saqlaydi, parallellikni qurbon qiladi. Tillar bu muvozanatdan uch xil yo'l bilan chiqmoqda: qulfni olib tashlash (Java/Go uslubi), izolyatsiya (JS Workers, Ruby Ractor, Python subinterpretatorlari) va jarayonlar. Python uchalasini ham ishlatayotgani — ekotizimning xilma-xilligiga mos, lekin tanlov murakkabligini dasturchiga yuklaydi.
1. Nega global qulf
| Sabab | Tafsilot |
|---|---|
| Havolalar sanog'i | Har murojaat — yozish; atom amallar bitta ipni sekinlashtiradi |
| Dinamik tuzilmalar | Lug'at asosidagi obyektlar, klasslar ish vaqtida o'zgaradi — nozik qulflar murakkab |
| C API | Kengaytma mualliflari sinxronizatsiya haqida o'ylamaydi |
| Tarix | Bitta yadro davrida qaror qilingan, keyin ekotizim unga bog'lanib qolgan |
2. Boshqa tillar
- CRuby GVL: Python GIL ning deyarli aynan o'zi; Ruby 3 Ractor — o'z holatiga ega, xabar almashadigan izolyatsiyalangan birliklar (Python subinterpretatorlariga o'xshash)
- JavaScript: asosiy ipda umumiy xotira yo'q; Web Workers va Node
worker_threads— xabar almashish,SharedArrayBufferfaqat raw baytlar uchun. Dizayn boshidan izolyatsiya - Java: JVM boshidan ko'p ipli; tracing GC havolalar sanog'isiz; har obyektda monitor (
synchronized). Narxi — dasturchi sinxronizatsiyani o'zi to'g'ri qilishi kerak - Go: goroutine va kanallar ("xotirani bo'lishib aloqa qilma, aloqa orqali xotirani bo'lish"), lekin umumiy xotira va
sync.Mutexham bor; poyga detektori (-race) - PyPy: GIL bor; STM (dasturiy tranzaksion xotira) tajribasi amaliyotga chiqmagan
3. Python ning uch yo'li
| Yo'l | Afzallik | Kamchilik |
|---|---|---|
| Erkin ipli yig'ma (PEP 703) | Umumiy xotira, eng kam ma'lumot ko'chirish | Ekotizim moslashishi kerak, bitta ipda biroz sekin, poyga xatolari ko'proq |
| Subinterpretatorlar (PEP 684, 734) | Bitta jarayon, har birida o'z GIL, izolyatsiya xavfsiz | Obyektlar ulashilmaydi, C kengaytmalar qo'llab-quvvatlashi kerak |
| Jarayonlar | Eng mustahkam, hozir ishlaydi | Ishga tushish va pickle narxi, xotira ko'payadi |
4. Strategiya to'g'rimi
- Ha, chunki ish turlari har xil: ML inferensiyasi (katta umumiy modellar) erkin ipli rejimdan, veb-serverlar izolyatsiyadan, ma'lumot quvurlari jarayonlardan foyda ko'radi
- Ha, chunki moslik saqlanadi: GIL li yig'ma sukut bo'lib qoladi, ekotizim bosqichma-bosqich o'tadi — Python 2→3 xatosi takrorlanmaydi
- Xavf: dasturchi uchun tanlov murakkablashadi; kutubxonalar uch rejimda sinalishi kerak
- Uzoq muddatda erkin ipli rejim sukutga aylansa, subinterpretatorlar izolyatsiya vositasi, jarayonlar esa xavfsizlik va nosozlikka chidamlilik vositasi bo'lib qoladi
5. Xulosa
- Global qulf — dinamik, havolalar sanog'iga asoslangan interpretator uchun eng arzon xavfsizlik
- Ruby Python bilan bir xil yo'lni bosib o'tmoqda (GVL → Ractor); JS izolyatsiyani, Java/Go umumiy xotira + dasturchi sinxronizatsiyasini tanlagan
- Python erkin ipli yig'ma, subinterpretatorlar va jarayonlar orqali uchala modelni taklif qilmoqda
- Qaysi til bo'lmasin: umumiy o'zgaruvchan holat — qulf yoki xabar almashish talab qiladi
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda GIL ni o'rgandik.
Eng muhim uch fikr:
GIL — bitta interpretatorda bir vaqtda bitta ip Python kodini bajarishi. U havolalar sanog'i, ichki tuzilmalar va C API ni qulfsiz, sodda va bitta ipda tez qilish uchun mavjud. Iplar haqiqiy OT iplari, lekin GIL ni navbat bilan oladi: kutayotgan ip 5 ms (
switchinterval) dan keyin so'rov yuboradi, bajarayotgan ip esa uni tekshiruv nuqtalarida bo'shatadi. I/O,sleep, qulf kutish va GIL ni bo'shatuvchi C kod (hashlibkatta ma'lumotda,zlib, NumPy) davomida boshqa iplar ishlaydi; sof Python hisoblash,jsonvareesa iplarda parallel bo'lmaydi.GIL sizning kodingizni himoya qilmaydi. U interpretatorni buzilishdan saqlaydi va
list.appendkabi bitta ichki amallarni amalda atomar qiladi, lekin "o'qi → hisobla → yoz" va "tekshir → bajar" ketma-ketliklari iplar orasida bo'linadi. Bu xatolar sukut sozlamalarida kamdan-kam ko'rinadi — shuning uchun qulf yoki navbat har doim kerak, stress-testlar esa kichikswitchintervalbilan.Kelajak — uch yo'l. 3.14 da erkin ipli yig'ma (
python3.14t, PEP 703/779) rasman qo'llab-quvvatlanadi, lekin sukut emas; subinterpretatorlar har birida o'z GIL bilan (concurrent.interpreters, PEP 734) va jarayonlar CPU ishni parallel qilishning boshqa yo'llari.
Keyingi darsda GIL qachon haqiqatan muammo ekanini o'lchaymiz: iplar, jarayonlar, subinterpretatorlar va asyncio ni turli ish turlarida solishtirib, strategiya tanlash qoidalarini chiqaramiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!