Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Ip yaratish
- 2.2. Natija va istisno "yo'qoladi"
- 2.3. ThreadPoolExecutor
- 2.4. Istisnolar
- 2.5. To'xtatish: Event bilan
- 2.6. threading.local
- 2.7. Boshqa vositalar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Thread asoslari
- Misol 2 — ThreadPoolExecutor
- Misol 3 — To'xtatish va ipga xos ma'lumot
- Misol 4 — Amaliy: chegaralangan yuklovchi
- 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
14.2-dars: threading asoslari
14-QISM — KONKURENTLIK VA PARALLELLIK · 2-dars
1. Kirish va motivatsiya
14.1-darsda uch modelni taqqosladik. Endi birinchisini amalda o'rganamiz: iplar — bloklovchi kutubxonalar bilan ishlaydigan I/O uchun eng qulay vosita.
Real vaziyat. Hisobot generatori 120 ta tashqi API dan ma'lumot yig'ardi, har biri ~1.5 soniya. Ketma-ket: 3 daqiqa. Jamoa threading.Thread bilan 120 ip yaratdi — vaqt 4 soniyaga tushdi, hamma xursand bo'ldi. Ikki hafta o'tib ikkita muammo chiqdi:
- Bitta API xato qaytarganda istisno yo'qoldi: ip jim o'ldi, natija ro'yxatida shunchaki qiymat kam bo'lib qoldi va hisobot noto'g'ri chiqdi.
- Xizmatni to'xtatishda (
Ctrl+C) yozilayotgan fayllar yarim qoldi — iplardaemon=Trueedi va ular darhol o'ldirildi.
Iplar bilan ishlashda asosiy qiyinchilik ularni yaratish emas — natijalarni yig'ish, xatolarni ushlash va to'g'ri to'xtatish. Shu uchta narsani concurrent.futures ancha soddalashtiradi.
Bu darsda:
-
Thread: yaratish,start,join, nom,daemon - Natijalar va istisnolar: nega ular "yo'qoladi"
-
ThreadPoolExecutor:submit,map,as_completed threading.excepthookva xatolarni ushlash- To'g'ri to'xtatish:
Eventbilan,daemonbilan emas threading.local— ipga xos ma'lumot- Amaliy: chegaralangan ishchilar puli va to'xtatish
2. Nazariya — chuqur tushuntirish
2.1. Ip yaratish
ip = threading.Thread(target=funksiya, args=(1, 2), kwargs={"x": 3}, name="yuklovchi", daemon=False)
ip.start() # ishga tushirish (faqat bir marta)
ip.join(timeout=5) # tugashini kutish
ip.is_alive() # hali ishlayaptimi| Xususiyat | Izoh |
|---|---|
target |
Ip ichida chaqiriladigan funksiya |
name |
Loglar uchun; sukut: Thread-N |
daemon |
True bo'lsa, dastur tugashida darhol o'ldiriladi |
join(timeout) |
Kutish; vaqt tugasa — davom etadi, ip esa ishlayveradi |
ip.start() ni ikki marta chaqirib bo'lmaydi; tugagan ipni qayta ishga tushirib ham bo'lmaydi.
Voris klass ham mumkin, lekin odatda target yetarli:
class Yuklovchi(threading.Thread):
def run(self) -> None:
self.natija = yukla()2.2. Natija va istisno "yo'qoladi"
def hisobla() -> int:
return 42
ip = threading.Thread(target=hisobla)
ip.start(); ip.join()
# natija qayerda? — hech qayerda| Muammo | Nima bo'ladi |
|---|---|
| Qaytgan qiymat | E'tiborsiz qoldiriladi |
| Istisno | Chaqiruvchiga yetib bormaydi; threading.excepthook ga boradi (sukut: stderr ga chop etadi) |
join() |
Xatoni ham, natijani ham qaytarmaydi |
Yechimlar:
ThreadPoolExecutor—Futurenatijani ham, istisnoni ham saqlaydi (tavsiya etiladi)- Umumiy ro'yxat/lug'at +
Lock(14.3-dars) queue.Queueorqali natijalarni yig'ish
2.3. ThreadPoolExecutor
with cf.ThreadPoolExecutor(max_workers=16, thread_name_prefix="yuklovchi") as ex:
kelajak = ex.submit(yukla, manzil) # bitta vazifa
natijalar = list(ex.map(yukla, manzillar)) # ko'p vazifa, tartib saqlanadi| Vosita | Nima qiladi |
|---|---|
submit(f, *args) |
Future qaytaradi |
future.result(timeout) |
Natija yoki istisno (qayta ko'tariladi) |
future.exception() |
Istisnoni obyekt sifatida |
map(f, ketma_ketlik) |
Tartibda natijalar; birinchi istisno iteratsiyada ko'tariladi |
as_completed(kelajaklar) |
Tugash tartibida |
with blokidan chiqish |
Hamma vazifa tugaguncha kutadi (shutdown(wait=True)) |
max_workers qancha bo'lsin:
| Ish | Tavsiya |
|---|---|
| I/O (tarmoq, disk) | 10–100 (kutish vaqtiga bog'liq); o'lchab tanlang |
| GIL ni bo'shatuvchi C kod | Yadrolar soni |
| Sof Python CPU | Iplar yordam bermaydi (12.6-dars) |
2.4. Istisnolar
threading.excepthook = lambda args: log.error("ipda xato: %s", args.exc_value)excepthook— barcha iplardagi ushlanmagan istisnolar uchun yagona nuqtaThreadPoolExecutorda istisnoFutureichida saqlanadi —result()chaqirilmasa, jim qoladi- Shuning uchun
submitqilgan har vazifaning natijasini albatta o'qing (as_completedyokiresult())
2.5. To'xtatish: Event bilan
toxta = threading.Event()
def ishchi() -> None:
while not toxta.is_set():
bajar()
tozala() # ✅ tozalash bajariladi
toxta.set() # to'xtatish signali
ip.join(timeout=5)| Usul | Xulq |
|---|---|
daemon=True |
Dastur tugashida darhol o'ladi: finally va with bajarilmaydi |
Event bilan |
Ip o'zi to'xtaydi, resurslarni yopadi |
join(timeout) |
Kutish; muddat tugasa, jurnalga yozib davom etish |
Python'da ipni majburan to'xtatish yo'q (thread.kill() yo'q) — faqat hamkorlikdagi to'xtatish.
2.6. threading.local
lokal = threading.local()
def ishchi() -> None:
lokal.sessiya = Sessiya() # har ipda alohida nusxa- Har ipga xos ma'lumot: ulanish, sessiya, joriy foydalanuvchi
- Korutinalarda esa
contextvarsishlatiladi (14.7-dars)
2.7. Boshqa vositalar
| Vosita | Nima uchun |
|---|---|
threading.Timer(5, f) |
Kechiktirilgan chaqiruv |
threading.Semaphore(n) |
Bir vaqtda n ta ishchi (chegaralash) |
threading.Barrier(n) |
Hamma tayyor bo'lguncha kutish |
threading.Event |
Signal berish |
Lock, RLock, Condition |
14.3-dars |
3. Tez ma'lumotnoma
import threading, concurrent.futures as cf
ip = threading.Thread(target=f, args=(1,), name="ishchi", daemon=False)
ip.start(); ip.join(timeout=5); ip.is_alive()
with cf.ThreadPoolExecutor(max_workers=16) as ex:
fut = ex.submit(f, x)
for f2 in cf.as_completed([fut]):
f2.result() # istisno shu yerda ko'tariladi
toxta = threading.Event(); toxta.set(); toxta.wait(timeout=1)
lokal = threading.local()
threading.excepthook = qayd_qil4. Batafsil misollar
Misol 1 — Thread asoslari
"""Yaratish, start/join, nom, is_alive; natija va istisno yo'qolishi; excepthook; voris klass."""
import threading
import time
JURNAL: list[str] = []
def ishchi(nomer: int, kutish: float = 0.05) -> int:
time.sleep(kutish)
JURNAL.append(f"{threading.current_thread().name} tugadi")
return nomer * 10
print("=== 1. Oddiy ip ===")
ip = threading.Thread(target=ishchi, args=(1,), name="ishchi-1")
print(f" ishga tushirishdan oldin tirikmi: {ip.is_alive()}")
ip.start()
print(f" ishga tushgach tirikmi: {ip.is_alive()}")
ip.join()
print(f" join dan keyin tirikmi: {ip.is_alive()}, jurnal: {JURNAL}")
print("\n=== 2. ⚠️ Natija yo'qoladi ===")
qaytgan = ip.join()
print(f" join() qaytargani: {qaytgan!r} — natija hech qayerda saqlanmadi")
print("\n=== 3. Natijani qo'lda yig'ish ===")
natijalar: dict[int, int] = {}
qulf = threading.Lock()
def yigib_ishla(nomer: int) -> None:
natija = ishchi(nomer)
with qulf:
natijalar[nomer] = natija
JURNAL.clear()
iplar = [threading.Thread(target=yigib_ishla, args=(i,), name=f"ip-{i}") for i in range(4)]
for i in iplar:
i.start()
for i in iplar:
i.join()
print(f" natijalar: {dict(sorted(natijalar.items()))}")
print(f" {len(JURNAL)} ta ip tugadi")
print("\n=== 4. ⚠️ Istisno chaqiruvchiga yetib bormaydi ===")
ushlangan: list[tuple[str, str, str]] = []
eski_hook = threading.excepthook
threading.excepthook = lambda args: ushlangan.append((args.exc_type.__name__, str(args.exc_value), args.thread.name))
def xato_qil() -> None:
raise ValueError("tashqi API javob bermadi")
xatoli = threading.Thread(target=xato_qil, name="xatoli-ip")
try:
xatoli.start()
xatoli.join()
print(" ⚠️ try/except hech narsa ushlamadi — istisno boshqa ipda qoldi")
except ValueError:
print(" (bu yerga tushmaydi)")
print(f" excepthook ushladi: {ushlangan}")
print(f" ip tugagan holatda: {not xatoli.is_alive()}")
threading.excepthook = eski_hook
print("\n=== 5. Voris klass va daemon ===")
class Yuklovchi(threading.Thread):
def __init__(self, manzil: str) -> None:
super().__init__(name=f"yuklovchi-{manzil}")
self.manzil = manzil
self.natija: str | None = None
def run(self) -> None:
time.sleep(0.02)
self.natija = f"{self.manzil}: 200 OK"
y = Yuklovchi("api.wisar.uz")
y.start()
y.join()
print(f" voris klass natijasi: {y.natija}")
fon = threading.Thread(target=time.sleep, args=(30,), daemon=True, name="fon-ip")
fon.start()
print(f" daemon ip: daemon={fon.daemon}, tirik={fon.is_alive()} — dastur tugashida majburan o'ldiriladi")
print(f" hozirgi faol iplar: {threading.active_count()} (asosiy + daemon)")Natijaning muhim qismi:
=== 1. Oddiy ip ===
ishga tushirishdan oldin tirikmi: False
ishga tushgach tirikmi: True
join dan keyin tirikmi: False, jurnal: ['ishchi-1 tugadi']
=== 2. ⚠️ Natija yo'qoladi ===
join() qaytargani: None — natija hech qayerda saqlanmadi
=== 3. Natijani qo'lda yig'ish ===
natijalar: {0: 0, 1: 10, 2: 20, 3: 30}
4 ta ip tugadi
=== 4. ⚠️ Istisno chaqiruvchiga yetib bormaydi ===
⚠️ try/except hech narsa ushlamadi — istisno boshqa ipda qoldi
excepthook ushladi: [('ValueError', 'tashqi API javob bermadi', 'xatoli-ip')]
ip tugagan holatda: True
=== 5. Voris klass va daemon ===
voris klass natijasi: api.wisar.uz: 200 OK
daemon ip: daemon=True, tirik=True — dastur tugashida majburan o'ldiriladi
hozirgi faol iplar: 2 (asosiy + daemon)Nima ko'rsatdi: 2.1, 2.2-bo'limlar.
Misol 2 — ThreadPoolExecutor
"""submit va map; Future; as_completed; istisnolar; max_workers ta'siri; nomlangan iplar."""
import concurrent.futures as cf
import threading
import time
KUTISH = 0.05
MANZILLAR = [f"api-{i}" for i in range(24)]
def yukla(manzil: str) -> str:
if manzil.endswith("7"):
raise ConnectionError(f"{manzil} javob bermadi")
time.sleep(KUTISH)
return f"{manzil}:{threading.current_thread().name}"
print("=== 1. map: tartib saqlanadi ===")
with cf.ThreadPoolExecutor(max_workers=8, thread_name_prefix="yuklovchi") as ex:
natijalar = list(ex.map(yukla, [m for m in MANZILLAR if not m.endswith("7")]))
print(f" {len(natijalar)} natija, birinchisi: {natijalar[0].split(':')[0]}, oxirgisi: {natijalar[-1].split(':')[0]}")
print(f" nechta xil ip ishlatildi: {len({n.split(':')[1] for n in natijalar})}")
print("\n=== 2. submit va Future ===")
with cf.ThreadPoolExecutor(max_workers=4) as ex:
kelajak = ex.submit(yukla, "api-1")
print(f" submit qaytargani: {type(kelajak).__name__}, tugadimi: {kelajak.done()}")
natija = kelajak.result(timeout=5)
print(f" result(): {natija.split(':')[0]}, endi tugadi: {kelajak.done()}")
print("\n=== 3. Istisnolar Future ichida ===")
with cf.ThreadPoolExecutor(max_workers=4) as ex:
yaxshi = ex.submit(yukla, "api-1")
yomon = ex.submit(yukla, "api-7")
print(f" yomon vazifa istisnosi: {type(yomon.exception()).__name__}")
try:
yomon.result()
except ConnectionError as xato:
print(f" result() istisnoni qayta ko'taradi: {xato}")
print(f" yaxshi vazifa ta'sirlanmadi: {yaxshi.result().split(':')[0]}")
print("\n=== 4. as_completed: tugash tartibida ===")
with cf.ThreadPoolExecutor(max_workers=8) as ex:
kelajaklar = {ex.submit(yukla, m): m for m in MANZILLAR}
muvaffaqiyat, xatolar = 0, []
for kelajak in cf.as_completed(kelajaklar):
manzil = kelajaklar[kelajak]
try:
kelajak.result()
muvaffaqiyat += 1
except ConnectionError as xato:
xatolar.append(manzil)
print(f" muvaffaqiyatli: {muvaffaqiyat}, xatolar: {sorted(xatolar)}")
print(" ⭐ har vazifaning natijasi o'qildi — jim yo'qolgan xato yo'q")
print("\n=== 5. max_workers ta'siri ===")
def olch(ishchilar: int) -> float:
toza = [m for m in MANZILLAR if not m.endswith("7")]
bosh = time.perf_counter()
with cf.ThreadPoolExecutor(max_workers=ishchilar) as ex:
list(ex.map(yukla, toza))
return time.perf_counter() - bosh
vaqtlar = {n: olch(n) for n in (1, 4, 16)}
print(f" 1 ishchi ketma-ketga yaqin: {vaqtlar[1] >= len(MANZILLAR) * KUTISH * 0.8}")
print(f" 4 ishchi 1 ishchidan kamida 3 barobar tez: {vaqtlar[1] / vaqtlar[4] >= 3}")
print(f" 16 ishchi 4 dan tez yoki teng: {vaqtlar[16] <= vaqtlar[4] * 1.2}")
print(" ⚠️ ishchilar sonini cheksiz oshirish foyda bermaydi — o'lchab tanlang")Natijaning muhim qismi:
=== 1. map: tartib saqlanadi ===
22 natija, birinchisi: api-0, oxirgisi: api-23
nechta xil ip ishlatildi: 8
=== 2. submit va Future ===
submit qaytargani: Future, tugadimi: False
result(): api-1, endi tugadi: True
=== 3. Istisnolar Future ichida ===
yomon vazifa istisnosi: ConnectionError
result() istisnoni qayta ko'taradi: api-7 javob bermadi
yaxshi vazifa ta'sirlanmadi: api-1
=== 4. as_completed: tugash tartibida ===
muvaffaqiyatli: 22, xatolar: ['api-17', 'api-7']
⭐ har vazifaning natijasi o'qildi — jim yo'qolgan xato yo'q
=== 5. max_workers ta'siri ===
1 ishchi ketma-ketga yaqin: True
4 ishchi 1 ishchidan kamida 3 barobar tez: True
16 ishchi 4 dan tez yoki teng: True
⚠️ ishchilar sonini cheksiz oshirish foyda bermaydi — o'lchab tanlangNima ko'rsatdi: 2.3, 2.4-bo'limlar.
Misol 3 — To'xtatish va ipga xos ma'lumot
"""daemon ip tozalashni bajarmaydi; Event bilan to'g'ri to'xtatish; join timeout; threading.local."""
import concurrent.futures as cf
import threading
import time
TOZALASHLAR: list[str] = []
def daemon_ishchi(toxta: threading.Event) -> None:
try:
while not toxta.is_set():
time.sleep(0.01)
finally:
TOZALASHLAR.append("daemon tozaladi")
def xushmuomala_ishchi(toxta: threading.Event, nom: str) -> None:
"""Signalni kutadi va o'zi to'xtaydi."""
try:
while not toxta.wait(timeout=0.01):
pass
finally:
TOZALASHLAR.append(f"{nom} tozaladi")
print("=== 1. Event bilan to'g'ri to'xtatish ===")
toxta = threading.Event()
iplar = [threading.Thread(target=xushmuomala_ishchi, args=(toxta, f"ishchi-{i}"), name=f"ishchi-{i}") for i in range(3)]
for ip in iplar:
ip.start()
print(f" ishlayotgan iplar: {sum(ip.is_alive() for ip in iplar)}")
toxta.set()
for ip in iplar:
ip.join(timeout=2)
print(f" to'xtatishdan keyin tirik: {sum(ip.is_alive() for ip in iplar)}")
print(f" tozalashlar: {sorted(TOZALASHLAR)}")
print("\n=== 2. join(timeout) ===")
uzoq = threading.Thread(target=time.sleep, args=(1.0,), name="uzoq-ip", daemon=True)
uzoq.start()
bosh = time.perf_counter()
uzoq.join(timeout=0.05)
oraliq = time.perf_counter() - bosh
print(f" join(0.05) tez qaytdi: {oraliq < 0.3}, lekin ip hali tirik: {uzoq.is_alive()}")
print(" ⭐ join muddati tugasa, dastur davom etadi — ipni tekshirish sizning zimmangizda")
print("\n=== 3. threading.local ===")
lokal = threading.local()
YARATILGAN: list[str] = []
def sessiya_ol() -> str:
"""Har ipda o'z 'ulanishi' bo'lsin."""
if not hasattr(lokal, "sessiya"):
lokal.sessiya = f"sessiya-{threading.current_thread().name}"
YARATILGAN.append(lokal.sessiya)
return lokal.sessiya
def vazifa(n: int) -> str:
sessiya = sessiya_ol()
time.sleep(0.01)
return f"{n}:{sessiya_ol()}" if sessiya == sessiya_ol() else "o'zgardi"
with cf.ThreadPoolExecutor(max_workers=3, thread_name_prefix="ishchi") as ex:
natijalar = list(ex.map(vazifa, range(9)))
sessiyalar = {n.split(":")[1] for n in natijalar}
print(f" 9 vazifa, {len(sessiyalar)} ta sessiya yaratildi (ishchilar soni bilan bir xil)")
print(f" har vazifada sessiya o'zgarmadi: {all(':' in n for n in natijalar)}")
print(f" yaratilgan sessiyalar: {len(YARATILGAN)}")
print("\n=== 4. Semaphore bilan chegaralash ===")
bir_vaqtda: list[int] = []
hozir = 0
hisob_qulfi = threading.Lock()
semafor = threading.Semaphore(2)
def cheklangan_ishchi() -> None:
global hozir
with semafor:
with hisob_qulfi:
hozir += 1
bir_vaqtda.append(hozir)
time.sleep(0.02)
with hisob_qulfi:
hozir -= 1
iplar = [threading.Thread(target=cheklangan_ishchi) for _ in range(8)]
for ip in iplar:
ip.start()
for ip in iplar:
ip.join()
print(f" 8 ip, semafor 2 ga cheklangan: eng ko'p bir vaqtda {max(bir_vaqtda)} ta ishladi")
print(f" chegara buzilmadi: {max(bir_vaqtda) <= 2}")Natijaning muhim qismi:
=== 1. Event bilan to'g'ri to'xtatish ===
ishlayotgan iplar: 3
to'xtatishdan keyin tirik: 0
tozalashlar: ['ishchi-0 tozaladi', 'ishchi-1 tozaladi', 'ishchi-2 tozaladi']
=== 2. join(timeout) ===
join(0.05) tez qaytdi: True, lekin ip hali tirik: True
⭐ join muddati tugasa, dastur davom etadi — ipni tekshirish sizning zimmangizda
=== 3. threading.local ===
9 vazifa, 3 ta sessiya yaratildi (ishchilar soni bilan bir xil)
har vazifada sessiya o'zgarmadi: True
yaratilgan sessiyalar: 3
=== 4. Semaphore bilan chegaralash ===
8 ip, semafor 2 ga cheklangan: eng ko'p bir vaqtda 2 ta ishladi
chegara buzilmadi: TrueNima ko'rsatdi: 2.5–2.7-bo'limlar.
Misol 4 — Amaliy: chegaralangan yuklovchi
Kirishdagi hisobot generatorini to'g'ri yozamiz: chegaralangan pul, har vazifaning natijasi o'qiladi, xatolar qayd etiladi va qayta urinish qilinadi, to'xtatish signali hurmat qilinadi, har ipda o'z "sessiyasi" bo'ladi.
"""Chegaralangan pul; qayta urinish; xatolarni yig'ish; to'xtatish signali; ipga xos sessiya; statistika."""
import concurrent.futures as cf
import threading
import time
from dataclasses import dataclass, field
MANZILLAR = [f"https://api-{i:02d}.wisar.uz/hisobot" for i in range(30)]
lokal = threading.local()
@dataclass
class Statistika:
muvaffaqiyat: int = 0
qayta_urinish: int = 0
xatolar: dict[str, str] = field(default_factory=dict)
sessiyalar: set[str] = field(default_factory=set)
qulf: threading.Lock = field(default_factory=threading.Lock)
def qayd(self, **maydonlar: object) -> None:
with self.qulf:
for nom, qiymat in maydonlar.items():
if nom == "xato":
manzil, sabab = qiymat # type: ignore[misc]
self.xatolar[manzil] = sabab
elif nom == "sessiya":
self.sessiyalar.add(qiymat) # type: ignore[arg-type]
else:
setattr(self, nom, getattr(self, nom) + 1)
def sessiya_ol() -> str:
if not hasattr(lokal, "sessiya"):
lokal.sessiya = f"sessiya-{threading.current_thread().name}"
return lokal.sessiya
URINISHLAR: dict[str, int] = {}
URINISH_QULFI = threading.Lock()
def xom_yukla(manzil: str) -> str:
"""Beqaror tashqi API (aniq qoida bilan, natija takrorlanadigan bo'lsin):
raqami 7 ga tugaydiganlar doim yiqiladi, 3 ga karralilari esa faqat birinchi urinishda."""
time.sleep(0.005)
raqam = int(manzil.split("-")[1].split(".")[0])
with URINISH_QULFI:
URINISHLAR[manzil] = URINISHLAR.get(manzil, 0) + 1
urinish = URINISHLAR[manzil]
if raqam % 10 == 7:
raise ConnectionError("xizmat o'chirilgan")
if raqam % 3 == 0 and urinish == 1:
raise ConnectionError("vaqtincha mavjud emas")
return f"{manzil} → 200"
def yukla(manzil: str, stat: Statistika, toxta: threading.Event, urinishlar: int = 3) -> str | None:
stat.qayd(sessiya=sessiya_ol())
for urinish in range(1, urinishlar + 1):
if toxta.is_set():
return None
try:
natija = xom_yukla(manzil)
stat.qayd(muvaffaqiyat=1)
return natija
except ConnectionError as xato:
if urinish == urinishlar:
stat.qayd(xato=(manzil, str(xato)))
return None
stat.qayd(qayta_urinish=1)
time.sleep(0.005 * urinish) # orqaga chekinish (backoff)
return None
def hisobot_yig(manzillar: list[str], ishchilar: int = 8) -> tuple[list[str], Statistika]:
stat = Statistika()
toxta = threading.Event()
natijalar: list[str] = []
with cf.ThreadPoolExecutor(max_workers=ishchilar, thread_name_prefix="yuklovchi") as ex:
kelajaklar = {ex.submit(yukla, m, stat, toxta): m for m in manzillar}
for kelajak in cf.as_completed(kelajaklar):
try:
natija = kelajak.result() # ⭐ har natija o'qiladi
except Exception as xato: # noqa: BLE001 — kutilmagan xato
stat.qayd(xato=(kelajaklar[kelajak], f"kutilmagan: {xato}"))
continue
if natija is not None:
natijalar.append(natija)
return natijalar, stat
print("=== 1. Yig'ish ===")
bosh = time.perf_counter()
natijalar, stat = hisobot_yig(MANZILLAR)
vaqt = time.perf_counter() - bosh
print(f" {len(MANZILLAR)} manzil, {len(natijalar)} muvaffaqiyatli, {len(stat.xatolar)} xato")
print(f" qayta urinishlar: {stat.qayta_urinish}")
print(f" ishlatilgan sessiyalar (iplar soni bilan bir xil): {len(stat.sessiyalar)}")
print(f" ketma-ket variantdan tez: {vaqt < len(MANZILLAR) * 0.01}")
print("\n=== 2. Xatolar yo'qolmadi ===")
for manzil, sabab in sorted(stat.xatolar.items()):
print(f" {manzil.split('//')[1].split('/')[0]}: {sabab}")
print(f" jami qayd etilgan xatolar: {len(stat.xatolar)}")
print(f" muvaffaqiyat + xato = jami: {len(natijalar) + len(stat.xatolar) == len(MANZILLAR)}")
print("\n=== 3. To'xtatish signali ===")
stat2 = Statistika()
toxta = threading.Event()
toxta.set() # darhol to'xtatish
with cf.ThreadPoolExecutor(max_workers=4) as ex:
kelajaklar = [ex.submit(yukla, m, stat2, toxta) for m in MANZILLAR[:10]]
natijalar2 = [k.result() for k in kelajaklar]
print(f" to'xtatilgan holatda natijalar: {natijalar2.count(None)} ta None, yuklanganlar: {stat2.muvaffaqiyat}")
print(" ⭐ ishchilar signalni tekshiradi va ish boshlamaydi (daemon bilan majburan o'ldirish emas)")
print("\n=== 4. Qoidalar ===")
print(" ✅ pul chegaralangan (max_workers), cheksiz ip yaratilmaydi")
print(" ✅ har Future natijasi o'qiladi — xato jim yo'qolmaydi")
print(" ✅ to'xtatish Event orqali, tozalash bajariladi")
print(" ✅ ipga xos holat threading.local da")Natijaning muhim qismi:
=== 1. Yig'ish ===
30 manzil, 27 muvaffaqiyatli, 3 xato
qayta urinishlar: 15
ishlatilgan sessiyalar (iplar soni bilan bir xil): 8
ketma-ket variantdan tez: True
=== 2. Xatolar yo'qolmadi ===
api-07.wisar.uz: xizmat o'chirilgan
api-17.wisar.uz: xizmat o'chirilgan
api-27.wisar.uz: xizmat o'chirilgan
jami qayd etilgan xatolar: 3
muvaffaqiyat + xato = jami: True
=== 3. To'xtatish signali ===
to'xtatilgan holatda natijalar: 10 ta None, yuklanganlar: 0
⭐ ishchilar signalni tekshiradi va ish boshlamaydi (daemon bilan majburan o'ldirish emas)
=== 4. Qoidalar ===
✅ pul chegaralangan (max_workers), cheksiz ip yaratilmaydi
✅ har Future natijasi o'qiladi — xato jim yo'qolmaydi
✅ to'xtatish Event orqali, tozalash bajariladi
✅ ipga xos holat threading.local daNima ko'rsatdi: 2.3–2.6-bo'limlar — kirishdagi ikki muammoning ikkalasi ham hal qilindi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"join() natijani qaytaradi" |
None qaytaradi — natijani o'zingiz saqlaysiz |
| "Ipdagi istisno dasturni to'xtatadi" | Faqat o'sha ip tugaydi; excepthook ga boradi |
"submit qilsam yetarli" |
result() o'qilmasa, istisno jim qoladi |
"daemon=True — qulay to'xtatish" |
Majburan o'ldiradi: finally bajarilmaydi |
"Ipni to'xtatish uchun kill bor" |
Yo'q — faqat hamkorlikdagi to'xtatish (Event) |
| "Ishchilar qancha ko'p bo'lsa shuncha tez" | Ma'lum nuqtadan keyin foyda yo'q |
"map istisnolarni yutadi" |
Iteratsiya paytida birinchi istisno ko'tariladi |
"threading.local — global o'zgaruvchi" |
Har ipda alohida nusxa |
6. Keng tarqalgan xatolar va yechimlari
1. Natijani global o'zgaruvchiga qulfsiz yozish
natijalar.append(x) # ⚠️ list.append atomar, lekin murakkab holat uchun qulf kerak
with qulf: natijalar.append(x) # ✅ yoki ThreadPoolExecutor2. submit natijasini o'qimaslik
for m in manzillar:
ex.submit(yukla, m) # ❌ xatolar jim yo'qoladi
for f in as_completed(kelajaklar):
f.result() # ✅3. Cheksiz ip yaratish
for u in 5000 * [...]:
threading.Thread(target=f).start() # ❌
with ThreadPoolExecutor(64) as ex: ... # ✅4. daemon=True bilan "to'xtatish"
ip = Thread(target=f, daemon=True) # ⚠️ fayl yarim yozilib qolishi mumkin
toxta = Event() # ✅ signal bilan5. join() ni unutish
ip.start() # ⚠️ dastur ipni kutmasdan davom etadi
ip.join() # ✅6. Ipdan istisnoni try/except bilan ushlashga urinish
try:
ip.start(); ip.join() # ❌ istisno bu yerda ko'rinmaydi
except Exception: ...7. CPU ishni iplarga berish
with ThreadPoolExecutor() as ex:
ex.map(og_ir_hisob, bolaklar) # ❌ GIL (12.6-dars)8. Pul ichidan pulga vazifa berish (o'zaro kutish)
def vazifa():
return EX.submit(boshqa).result() # ⚠️ hamma ishchi band bo'lsa — deadlock7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 12.5-dars (o'tilgan): GIL va iplarning cheklovlari
- 14.1-dars (o'tilgan): modellar taqqoslovi
- 14.3-dars: poyga holatlari,
Lock,Queue - 14.5-dars:
concurrent.futureschuqurroq, jarayonlar bilan - 14.7-dars:
asyncio.to_thread— bloklovchi kodni ulash - 17-qism: konkurent kodni testlash
- 20-qism: veb-serverlar: ip pullari
- 26-qism: avtomatlashtirish skriptlari
8. Eng yaxshi amaliyotlar
ThreadPoolExecutorni sukut tanlov qiling —Threadfaqat maxsus holatlarda.Har
Futurenatijasini o'qing.Iplarga nom bering (
thread_name_prefix) — loglar tushunarli bo'ladi.To'xtatishni
Eventbilan qiling,daemonga tayanmang.max_workersni o'lchab tanlang.Ipga xos resurslar —
threading.local.Umumiy holatni qulf bilan himoyalang (14.3-dars).
Xatolarni yig'ib, hisobot qiling — jim yo'qolmasin.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import threading
import concurrent.futures as cf
1. ip = threading.Thread(target=lambda: None)
print(ip.is_alive())
2. ip.start(); print(ip.join())
3. print(ip.is_alive())
4. try:
ip.start()
except RuntimeError:
print("RuntimeError")
5. print(threading.Thread(target=print).daemon)
6. with cf.ThreadPoolExecutor(2) as ex:
f = ex.submit(sum, [1, 2, 3])
print(f.result(), f.done())
7. with cf.ThreadPoolExecutor(2) as ex:
f = ex.submit(lambda: 1 / 0)
print(type(f.exception()).__name__)
8. with cf.ThreadPoolExecutor(2) as ex:
print(list(ex.map(str, [1, 2])))
9. e = threading.Event()
print(e.is_set(), e.wait(timeout=0.01))
10. e.set(); print(e.is_set(), e.wait(timeout=0.01))
11. lokal = threading.local()
lokal.x = 5
print(hasattr(lokal, "x"))
12. s = threading.Semaphore(1)
print(s.acquire(blocking=False), s.acquire(blocking=False))Javoblar
FalseNone—joinnatija qaytarmaydiFalseRuntimeError— tugagan ipni qayta ishga tushirib bo'lmaydiFalse— sukut bo'yicha daemon emas6 TrueZeroDivisionError['1', '2']False FalseTrue TrueTrueTrue False
Vazifa 2: Xatolarni tuzating
1. natijalar = []
def ishchi(n):
natijalar.append(og_ir_yukla(n))
for n in range(100):
threading.Thread(target=ishchi, args=(n,)).start()
2. with ThreadPoolExecutor(8) as ex:
for m in manzillar:
ex.submit(yukla, m)
print("hammasi yuklandi")
3. ip = threading.Thread(target=fayl_yoz, daemon=True)
ip.start()
# dastur tugaydi
4. def yukla_hammasi(manzillar):
iplar = [threading.Thread(target=yukla, args=(m,)) for m in manzillar]
for i in iplar: i.start()
return "tayyor"
5. with ThreadPoolExecutor(4) as ex:
natijalar = list(ex.map(og_ir_hisob, bolaklar)) # sof Python CPUJavoblar
1. with ThreadPoolExecutor(max_workers=16) as ex: # chegaralangan pul
natijalar = list(ex.map(og_ir_yukla, range(100)))
2. with ThreadPoolExecutor(8) as ex:
kelajaklar = {ex.submit(yukla, m): m for m in manzillar}
for k in as_completed(kelajaklar):
k.result() # xatolar ko'rinadi
print("hammasi yuklandi")
3. toxta = threading.Event()
ip = threading.Thread(target=fayl_yoz, args=(toxta,)) # daemon emas
ip.start()
toxta.set(); ip.join(timeout=5) # tozalashga ulgursin
4. def yukla_hammasi(manzillar):
iplar = [threading.Thread(target=yukla, args=(m,)) for m in manzillar]
for i in iplar: i.start()
for i in iplar: i.join() # kutish shart
return "tayyor"
5. with ProcessPoolExecutor(4) as ex: # CPU uchun jarayonlar
natijalar = list(ex.map(og_ir_hisob, bolaklar))Vazifa 3: Yuklovchi vositasi
CLI vosita yozing: manzillar ro'yxatini faylni o'qib yuklaydi.
ThreadPoolExecutorbilan,--ishchilarbayrog'i- Har manzil uchun: holat kodi, vaqt, hajm
- Xatolarni alohida faylga yozish
Ctrl+Cbosilganda xushmuomala to'xtatish (Event+signal)- Yakunda statistika: muvaffaqiyat, xato, o'rtacha vaqt, p95
Vazifa 4: max_workers egri chizig'i
- Bir xil I/O yukni 1, 2, 4, 8, 16, 32, 64, 128 ishchi bilan o'lchang
- Vaqt va xotirani qayd eting
- "Foyda to'xtaydigan" nuqtani toping va sababini tushuntiring
- Kutish vaqtini (0.01 s, 0.1 s, 1 s) o'zgartirib, egri chiziq qanday siljishini ko'rsating
Vazifa 5: Xatolarni ushlash
threading.excepthookni o'rnatib, barcha ipdagi xatolarni faylga yozingThreadPoolExecutorbilan bir xil natijaga erishing (Futureorqali)- Ikki usulni solishtiring: qaysi biri qanday ma'lumot beradi
- Xatolarni turlari bo'yicha guruhlab hisobot chiqaring
Vazifa 6: Xushmuomala to'xtatish
- Uzoq ishlaydigan uch ipli xizmat yozing (har biri fayl yozadi)
Eventbilan to'xtatish vajoin(timeout)bilan kutish qo'shing- Muddat ichida tugamagan iplarni qayd eting
- Xuddi shu xizmatni
daemon=Truebilan yozib, fayllar holatini solishtiring - Natijani "nima uchun daemon xavfli" hisoboti qilib yozing
Vazifa 7: O'ylash
Java'da ExecutorService va CompletableFuture, .NET'da Task va async/await, Go'da esa go kalit so'zi va WaitGroup. Python'dagi Thread + ThreadPoolExecutor modeli ular bilan qanday taqqoslanadi va nega Python'da "ipni to'xtatish" (kill) umuman yo'q?
Javob
Qisqa javob: ThreadPoolExecutor Java'dagi ExecutorService ning bevosita o'xshashi (u aslida shundan ilhomlangan), Future esa CompletableFuture ning soddaroq varianti. Ipni majburan to'xtatish esa deyarli hamma zamonaviy platformada rad etilgan: Java'da Thread.stop() eskirgan, .NET'da Thread.Abort() olib tashlangan, Go'da goroutine ni tashqaridan to'xtatib bo'lmaydi.
1. Taqqoslash
| Platforma | Yaratish | Pul | Natija |
|---|---|---|---|
| Java | new Thread() |
ExecutorService |
Future, CompletableFuture |
| .NET | Thread |
ThreadPool |
Task<T> |
| Go | go f() |
Qo'lda (worker pool naqshi) | Kanallar |
| Python | threading.Thread |
ThreadPoolExecutor |
Future |
2. Nega kill yo'q
Ipni ixtiyoriy nuqtada to'xtatish:
- Qulflar ochilmay qoladi: ip
Lockni ushlab turganda o'lsa, boshqalar abadiy kutadi - Invariantlar buziladi: ma'lumot tuzilmasi yarim o'zgargan holatda qoladi
- Resurslar yopilmaydi:
finallyvawithbajarilmaydi - Xotira holati noaniq: C kengaytmalar ichida to'xtatish — yiqilish
Java'da Thread.stop() aynan shu sabablarga ko'ra 2000-yillarning boshida eskirgan deb e'lon qilingan. Zamonaviy yondashuv — hamkorlikdagi bekor qilish: Go'da context.Context, .NET'da CancellationToken, Python'da Event yoki asyncio da Task.cancel().
3. asyncio dagi farq
vazifa.cancel() # korutinaga CancelledError yuboriladi — await nuqtasidaKorutinalar to'xtatish nuqtalari aniq (await) bo'lgani uchun bekor qilish xavfsiz: kod try/finally bilan tozalashni bajaradi (14.7-dars).
4. Amaliy tavsiyalar
- Har uzoq ishlaydigan ipda
Event(yokiasyncioda bekor qilish) ni qo'llab-quvvatlang daemon=Trueni faqat "yo'qotilishi mumkin bo'lgan" fon ishlari uchun ishlating- Tozalashni
finallyga qo'ying va uni bajarish imkonini bering - To'xtatish muddatini belgilang (
join(timeout)) va muddat tugasa — qayd eting
Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.
Xulosa
Bu darsda iplar bilan amaliy ishlashni o'rgandik.
Eng muhim uch fikr:
Threadnatijani ham, istisnoni ham qaytarmaydi.join()—None; funksiyaning qaytgan qiymati e'tiborsiz qoldiriladi; ushlanmagan istisno esathreading.excepthookga boradi va chaqiruvchi unitry/exceptbilan ushlay olmaydi. Shuning uchun sukut tanlov —ThreadPoolExecutor:Futurenatijani ham, xatoni ham saqlaydi.Har
Futureo'qilishi shart.submitqilib,result()ni chaqirmaslik — xatolarni jim yo'qotishning eng keng tarqalgan yo'li.as_completedbilan tugash tartibida yig'ing,mapbilan tartibni saqlang;max_workersni esa o'lchab tanlang — ma'lum nuqtadan keyin ishchilarni ko'paytirish foyda bermaydi.To'xtatish — hamkorlikda. Python'da ipni majburan o'ldirish yo'q:
Eventbilan signal bering, ip o'zi tsiklni tugatib,finallyda tozalasin, siz esajoin(timeout)bilan kuting.daemon=True— bu tozalashni bajarmaydi: dastur tugashida ip darhol o'ladi va yarim yozilgan fayllar qoladi.
Keyingi darsda iplarning eng xavfli tomoniga o'tamiz: poyga holatlari, Lock va RLock, deadlock, Queue bilan xavfsiz ma'lumot almashish va atomar amallar haqidagi tasavvurlar.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!