IlmHamroh
Python kursi/Konkurentlik va parallellik2/8-dars21 daqiqa
Mundarija (21)

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:

  1. 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.
  2. Xizmatni to'xtatishda (Ctrl+C) yozilayotgan fayllar yarim qoldi — iplar daemon=True edi 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.excepthook va xatolarni ushlash
  • To'g'ri to'xtatish: Event bilan, daemon bilan emas
  • threading.local — ipga xos ma'lumot
  • Amaliy: chegaralangan ishchilar puli va to'xtatish

2. Nazariya — chuqur tushuntirish

2.1. Ip yaratish

python
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:

python
class Yuklovchi(threading.Thread):
    def run(self) -> None:
        self.natija = yukla()

2.2. Natija va istisno "yo'qoladi"

python
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:

  1. ThreadPoolExecutor — Future natijani ham, istisnoni ham saqlaydi (tavsiya etiladi)
  2. Umumiy ro'yxat/lug'at + Lock (14.3-dars)
  3. queue.Queue orqali natijalarni yig'ish

2.3. ThreadPoolExecutor

python
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

python
threading.excepthook = lambda args: log.error("ipda xato: %s", args.exc_value)
  • excepthook — barcha iplardagi ushlanmagan istisnolar uchun yagona nuqta
  • ThreadPoolExecutor da istisno Future ichida saqlanadi — result() chaqirilmasa, jim qoladi
  • Shuning uchun submit qilgan har vazifaning natijasini albatta o'qing (as_completed yoki result())

2.5. To'xtatish: Event bilan

python
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

python
lokal = threading.local()

def ishchi() -> None:
    lokal.sessiya = Sessiya()      # har ipda alohida nusxa
  • Har ipga xos ma'lumot: ulanish, sessiya, joriy foydalanuvchi
  • Korutinalarda esa contextvars ishlatiladi (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

python
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_qil

4. Batafsil misollar

Misol 1 — Thread asoslari

python
"""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:

text
=== 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

python
"""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:

text
=== 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 tanlang

Nima ko'rsatdi: 2.3, 2.4-bo'limlar.

Misol 3 — To'xtatish va ipga xos ma'lumot

python
"""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:

text
=== 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: True

Nima 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.

python
"""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:

text
=== 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 da

Nima 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

python
natijalar.append(x)                 # ⚠️ list.append atomar, lekin murakkab holat uchun qulf kerak
with qulf: natijalar.append(x)      # ✅ yoki ThreadPoolExecutor

2. submit natijasini o'qimaslik

python
for m in manzillar:
    ex.submit(yukla, m)             # ❌ xatolar jim yo'qoladi
for f in as_completed(kelajaklar):
    f.result()                      # ✅

3. Cheksiz ip yaratish

python
for u in 5000 * [...]:
    threading.Thread(target=f).start()   # ❌
with ThreadPoolExecutor(64) as ex: ...   # ✅

4. daemon=True bilan "to'xtatish"

python
ip = Thread(target=f, daemon=True)   # ⚠️ fayl yarim yozilib qolishi mumkin
toxta = Event()                      # ✅ signal bilan

5. join() ni unutish

python
ip.start()                           # ⚠️ dastur ipni kutmasdan davom etadi
ip.join()                            # ✅

6. Ipdan istisnoni try/except bilan ushlashga urinish

python
try:
    ip.start(); ip.join()            # ❌ istisno bu yerda ko'rinmaydi
except Exception: ...

7. CPU ishni iplarga berish

python
with ThreadPoolExecutor() as ex:
    ex.map(og_ir_hisob, bolaklar)    # ❌ GIL (12.6-dars)

8. Pul ichidan pulga vazifa berish (o'zaro kutish)

python
def vazifa():
    return EX.submit(boshqa).result()   # ⚠️ hamma ishchi band bo'lsa — deadlock

7. 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.futures chuqurroq, 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

  1. ThreadPoolExecutor ni sukut tanlov qiling — Thread faqat maxsus holatlarda.

  2. Har Future natijasini o'qing.

  3. Iplarga nom bering (thread_name_prefix) — loglar tushunarli bo'ladi.

  4. To'xtatishni Event bilan qiling, daemon ga tayanmang.

  5. max_workers ni o'lchab tanlang.

  6. Ipga xos resurslar — threading.local.

  7. Umumiy holatni qulf bilan himoyalang (14.3-dars).

  8. Xatolarni yig'ib, hisobot qiling — jim yo'qolmasin.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
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
  1. False
  2. None — join natija qaytarmaydi
  3. False
  4. RuntimeError — tugagan ipni qayta ishga tushirib bo'lmaydi
  5. False — sukut bo'yicha daemon emas
  6. 6 True
  7. ZeroDivisionError
  8. ['1', '2']
  9. False False
  10. True True
  11. True
  12. True False

Vazifa 2: Xatolarni tuzating

python
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 CPU
Javoblar
python
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.

  1. ThreadPoolExecutor bilan, --ishchilar bayrog'i
  2. Har manzil uchun: holat kodi, vaqt, hajm
  3. Xatolarni alohida faylga yozish
  4. Ctrl+C bosilganda xushmuomala to'xtatish (Event + signal)
  5. Yakunda statistika: muvaffaqiyat, xato, o'rtacha vaqt, p95

Vazifa 4: max_workers egri chizig'i

  1. Bir xil I/O yukni 1, 2, 4, 8, 16, 32, 64, 128 ishchi bilan o'lchang
  2. Vaqt va xotirani qayd eting
  3. "Foyda to'xtaydigan" nuqtani toping va sababini tushuntiring
  4. Kutish vaqtini (0.01 s, 0.1 s, 1 s) o'zgartirib, egri chiziq qanday siljishini ko'rsating

Vazifa 5: Xatolarni ushlash

  1. threading.excepthook ni o'rnatib, barcha ipdagi xatolarni faylga yozing
  2. ThreadPoolExecutor bilan bir xil natijaga erishing (Future orqali)
  3. Ikki usulni solishtiring: qaysi biri qanday ma'lumot beradi
  4. Xatolarni turlari bo'yicha guruhlab hisobot chiqaring

Vazifa 6: Xushmuomala to'xtatish

  1. Uzoq ishlaydigan uch ipli xizmat yozing (har biri fayl yozadi)
  2. Event bilan to'xtatish va join(timeout) bilan kutish qo'shing
  3. Muddat ichida tugamagan iplarni qayd eting
  4. Xuddi shu xizmatni daemon=True bilan yozib, fayllar holatini solishtiring
  5. 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:

  1. Qulflar ochilmay qoladi: ip Lock ni ushlab turganda o'lsa, boshqalar abadiy kutadi
  2. Invariantlar buziladi: ma'lumot tuzilmasi yarim o'zgargan holatda qoladi
  3. Resurslar yopilmaydi: finally va with bajarilmaydi
  4. 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

python
vazifa.cancel()          # korutinaga CancelledError yuboriladi — await nuqtasida

Korutinalar to'xtatish nuqtalari aniq (await) bo'lgani uchun bekor qilish xavfsiz: kod try/finally bilan tozalashni bajaradi (14.7-dars).

4. Amaliy tavsiyalar

  1. Har uzoq ishlaydigan ipda Event (yoki asyncio da bekor qilish) ni qo'llab-quvvatlang
  2. daemon=True ni faqat "yo'qotilishi mumkin bo'lgan" fon ishlari uchun ishlating
  3. Tozalashni finally ga qo'ying va uni bajarish imkonini bering
  4. 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:

  1. Thread natijani ham, istisnoni ham qaytarmaydi. join() — None; funksiyaning qaytgan qiymati e'tiborsiz qoldiriladi; ushlanmagan istisno esa threading.excepthook ga boradi va chaqiruvchi uni try/except bilan ushlay olmaydi. Shuning uchun sukut tanlov — ThreadPoolExecutor: Future natijani ham, xatoni ham saqlaydi.

  2. Har Future o'qilishi shart. submit qilib, result() ni chaqirmaslik — xatolarni jim yo'qotishning eng keng tarqalgan yo'li. as_completed bilan tugash tartibida yig'ing, map bilan tartibni saqlang; max_workers ni esa o'lchab tanlang — ma'lum nuqtadan keyin ishchilarni ko'paytirish foyda bermaydi.

  3. To'xtatish — hamkorlikda. Python'da ipni majburan o'ldirish yo'q: Event bilan signal bering, ip o'zi tsiklni tugatib, finally da tozalasin, siz esa join(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.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
14.2-dars: threading asoslari — IlmHamroh