IlmHamroh
Python kursi/Konkurentlik va parallellik6/8-dars23 daqiqa
Mundarija (22)

14.6-dars: asyncio — hodisa sikli

14-QISM — KONKURENTLIK VA PARALLELLIK · 6-dars


1. Kirish va motivatsiya

Bitta savol bilan boshlaymiz: 10 000 ta ochiq ulanishni bir vaqtda qanday ushlab turasiz?

Iplar bilan — 10 000 ip kerak: har biriga kamida 8 KB stek, kontekst almashtirish, operatsion tizim chegarasi. Jarayonlar bilan — umuman imkonsiz. Lekin bu ulanishlarning 99% i hech narsa qilmayapti: ular ma'lumot kutayapti.

asyncio shu haqiqatdan foydalanadi: kutayotgan vazifa protsessorni ushlab turmasligi kerak. Bitta ipda bitta hodisa sikli (event loop) minglab vazifani navbat bilan bajaradi — har biri await ga kelganda boshqasiga yo'l beradi.

Real vaziyat. Chat serveri 800 ta bir vaqtdagi ulanishda "ip yaratib bo'lmadi" xatosi bilan yiqilardi. asyncio ga o'tkazilgandan keyin o'sha server bitta jarayonda 30 000 ulanishni ushlab turdi — chunki ulanishning ip emas, atigi bir necha kilobaytlik obyekt bo'lib qoldi.

Bu darsda mexanizmni ichidan ko'ramiz:

  • Hodisa sikli qanday ishlaydi: navbat, call_soon, await nuqtalari
  • Korutina obyekti, Task, Future — farqi nimada
  • gather, TaskGroup, as_completed, wait
  • Bekor qilish va CancelledError
  • Loopni bloklash — eng ko'p uchraydigan xato
  • to_thread, run_in_executor — bloklovchi kodni ko'chirish
  • Nosozlik izlash: asyncio.run(..., debug=True)

2. Nazariya — chuqur tushuntirish

2.1. Hodisa sikli nima

Hodisa sikli — bu cheksiz tsikl: tayyor vazifalarni navbatdan olib, ularni await ga duch kelguncha bajaradi.

while ishlayapti:
    1. Tayyor navbatdagi har bir callback ni bajar
    2. Tayyor bo'lgan I/O ni tekshir (select/epoll/IOCP)
    3. Vaqti kelgan taymerlarni tayyor navbatga ko'chir
Tushuncha Ma'nosi
Korutina funksiya async def bilan e'lon qilingan funksiya
Korutina obyekti Uni chaqirganda hosil bo'ladigan obyekt — hali bajarilmaydi
Task Loop rejalashtirgan korutina (Future merosxo'ri)
Future "Natija keyinroq bo'ladi" degan va'da
await Nazoratni loopga qaytarish nuqtasi

Korutinani chaqirish uni ishga tushirmaydi. Ishga tushirish uchun await yoki create_task kerak.

python
k = ishla()             # hech narsa bajarilmadi
natija = await k        # endi bajariladi
vazifa = asyncio.create_task(ishla())   # fon rejimida rejalashtirildi

2.2. Kirish nuqtasi

python
asyncio.run(main())                     # ⭐ yagona to'g'ri kirish nuqtasi
asyncio.run(main(), debug=True)         # nosozlik izlash rejimi

asyncio.run: yangi loop yaratadi → main() ni bajaradi → qolgan vazifalarni bekor qiladi → loopni yopadi.

Funksiya Qachon
asyncio.run(main()) Dasturning kirish nuqtasi
asyncio.get_running_loop() Korutina ichida loopga kirish
asyncio.get_event_loop() Eskirgan uslub — ishlatmang
loop.run_until_complete() Faqat maxsus holatlarda

Loopdan tashqarida get_running_loop() → RuntimeError: no running event loop.

2.3. Task — fon vazifasi

python
vazifa = asyncio.create_task(ishla(), name="yuklovchi")
vazifa.done() / vazifa.result() / vazifa.cancel() / vazifa.get_name()
natija = await vazifa
Xususiyat Izoh
Yaratilganda Navbatga qo'yiladi, keyingi await da boshlanadi
Future merosxo'ri isinstance(task, asyncio.Future) → True
Havolasiz qolsa Yig'ib tashlanishi mumkin — havolani saqlang
Istisno o'qilmasa "Task exception was never retrieved" ogohlantirishi

Eager task factory (3.12+): loop.set_task_factory(asyncio.eager_task_factory) — vazifa yaratilishi bilanoq birinchi await gacha bajariladi (tezroq, lekin tartib o'zgaradi).

2.4. Ko'p vazifani birga bajarish

Vosita Xususiyati
await asyncio.gather(*k) Hammasini kutadi; natijalar kirish tartibida
gather(..., return_exceptions=True) Xatolar natija sifatida qaytadi
async with asyncio.TaskGroup() 3.11+: bittasi yiqilsa — qolganlari bekor qilinadi
asyncio.as_completed(k) Tugash tartibida
await asyncio.wait(k, timeout=) (tugagan, qolgan) to'plamlari
python
async with asyncio.TaskGroup() as guruh:        # ⭐ zamonaviy tanlov
    guruh.create_task(birinchi())
    guruh.create_task(ikkinchi())
# bu yerda ikkalasi ham tugagan; xato bo'lsa — ExceptionGroup

TaskGroup xatolarni ExceptionGroup ga yig'adi va except* bilan ushlanadi (9.4-dars).

2.5. Bekor qilish

python
vazifa.cancel()
try:
    await vazifa
except asyncio.CancelledError:
    ...
Fakt Izoh
CancelledError — BaseException except Exception uni ushlamaydi
Bekor qilish await nuqtasida yetadi Bloklangan kod bekor qilinmaydi
finally bajariladi Tozalash uchun ishonchli joy
asyncio.shield(k) Bekor qilishdan himoya qiladi

Muddatlar:

python
async with asyncio.timeout(5):          # ⭐ 3.11+
    await sekin()
natija = await asyncio.wait_for(sekin(), timeout=5)

Ikkalasi ham TimeoutError beradi va vazifani haqiqatan bekor qiladi — bu concurrent.futures dagi muddatdan muhim farq (14.5-dars).

2.6. Loopni bloklash

Eng ko'p uchraydigan asyncio xatosi. Bitta ipda hamma vazifa bajariladi — bitta bloklovchi chaqiruv hammasini to'xtatadi.

Bloklovchi O'rniga
time.sleep(1) await asyncio.sleep(1)
requests.get(...) aiohttp / httpx.AsyncClient
open(...).read() — katta fayl await asyncio.to_thread(...)
Og'ir hisob (sof Python) run_in_executor(ProcessPoolExecutor(), ...)
input(), psycopg2 va boshqalar Asinxron variant yoki to_thread
python
natija = await asyncio.to_thread(bloklovchi_funksiya, arg)       # ⭐ sodda yo'l
natija = await loop.run_in_executor(jarayonlar_puli, ogir, arg)  # CPU uchun

2.7. Navbat va tartib

python
loop.call_soon(f, arg)              # keyingi aylanishda
loop.call_later(0.5, f)             # 0.5 s dan keyin
await asyncio.sleep(0)              # ⭐ navbatni boshqalarga berish

await asyncio.sleep(0) — nazoratni loopga qaytarishning eng arzon usuli: uzoq tsikl ichida boshqa vazifalarga yo'l ochadi.

2.8. Platforma, uvloop va zamonaviy CPython evolyutsiyasi

Platforma Standart loop Imkoniyatlar
Linux/macOS SelectorEventLoop (epoll/kqueue) Standart, toza Python/C aralash
Windows ProactorEventLoop (IOCP) Windows I/O Completion Ports
Yuqori tezlik (ishlab chiqarish) uvloop (Linux/macOS) libuv (Node.js yadrosi) ustiga Cython'da qurilgan

1. uvloop — 2x-4x tezkor hodisa sikli

Standart asyncio sikli sof Pythonda yozilgan qismlarga ega. uvloop esa asyncio.AbstractEventLoop interfeysini to'liq qamrab olgan va Cython'da yozilgan. U Python asinxron tarmoq tezligini Go va Node.js darajasiga yaqinlashtiradi.

python
# O'rnatish: pip install uvloop
import asyncio
import uvloop

# 1-usul: Python 3.11+ da loop_factory orqali
asyncio.run(main(), loop_factory=uvloop.new_event_loop)

# 2-usul: Global hodisa sikli siyosatini o'rnatish
uvloop.install()
asyncio.run(main())

# 3-usul: FastAPI / Uvicorn bilan ishlab chiqarishda:
# uvicorn main:app --loop uvloop --http httptools

2. Python 3.12 va 3.13: GIL inqilobi

Konkurentlik va parallellik tarixida Python 3.12/3.13 eng katta burilish nuqtasi bo'ldi:

  • Free-threaded CPython (PEP 703 — Python 3.13): Python 3.13 dan boshlab GIL'ni butunlay o'chirib ishlatish mumkin (python3.13t yoki --disable-gil). Bu rejimda threading.Thread orqali CPU-bound hisob-kitoblar bir jarayon ichida bir nechta CPU yadrolarida haqiqiy parallel ishlaydi.
  • Per-Interpreter GIL va Subinterpreters (PEP 684 / PEP 554): Bitta Python jarayoni ichida bir nechta mustaqil sub-interpreterlar yaratiladi va har biri o'zining shaxsiy GIL'iga ega bo'ladi. Ular xotirani bo'lishmaydi, IPC (jarayonlararo aloqa) xarajatisiz haqiqiy parallellik beradi.
python
asyncio.run(main(), debug=True)     # sekin callbacklar va unutilgan korutinalar haqida ogohlantiradi

3. Tez ma'lumotnoma

python
import asyncio

async def main() -> None:
    async with asyncio.TaskGroup() as guruh:
        guruh.create_task(ishla(1))
        guruh.create_task(ishla(2))

    async with asyncio.timeout(5):
        await sekin()

    natija = await asyncio.to_thread(bloklovchi, arg)

asyncio.run(main())

Qoidalar

async def → korutina obyekti; await yoki create_task kerak
TaskGroup — sukut tanlov, gather — oddiy holatlar uchun
CancelledError — BaseException, uni yutmang
loopda bloklovchi chaqiruv yo'q
to_thread — I/O uchun, ProcessPool — hisob uchun

4. Batafsil misollar

Misol 1 — Hodisa sikli ichidan

python
"""Korutina obyekti va Task; loopga kirish; call_soon navbati; sleep(0) bilan navbat almashish; eager task."""

import asyncio
import inspect


async def salom() -> int:
    return 42


async def loop_haqida() -> None:
    loop = asyncio.get_running_loop()
    print(f"  AbstractEventLoop merosxo'ri: {isinstance(loop, asyncio.AbstractEventLoop)}")
    print(f"  ishlayapti: {loop.is_running()}, debug rejimi: {loop.get_debug()}")


async def vazifa_haqida() -> None:
    vazifa = asyncio.create_task(salom(), name="salom-vazifa")
    print(f"  turi: {type(vazifa).__name__}, Future merosxo'ri: {isinstance(vazifa, asyncio.Future)}")
    print(f"  yaratilgan zahoti done(): {vazifa.done()}   ← hali boshlanmagan")
    natija = await vazifa
    print(f"  await dan keyin: natija={natija}, done={vazifa.done()}, nom={vazifa.get_name()}")


async def navbat_tartibi() -> None:
    jurnal: list[str] = []
    loop = asyncio.get_running_loop()
    loop.call_soon(jurnal.append, "call_soon-1")
    loop.call_soon(jurnal.append, "call_soon-2")
    jurnal.append("korutina-davom-etdi")
    await asyncio.sleep(0)
    print(f"  {jurnal}")
    print("  ⭐ korutina await gacha ishlaydi, keyin navbatdagilar")


async def navbat_almashish() -> None:
    jurnal: list[str] = []

    async def hisobla(nom: str) -> None:
        for i in range(3):
            jurnal.append(f"{nom}{i}")
            await asyncio.sleep(0)             # ⭐ navbatni boshqaga berish

    await asyncio.gather(hisobla("a"), hisobla("b"))
    print(f"  {jurnal}")
    print("  ⭐ sleep(0) har safar navbatni almashtiradi")


async def tezkor_vazifa() -> None:
    loop = asyncio.get_running_loop()
    loop.set_task_factory(asyncio.eager_task_factory)
    jurnal: list[str] = []

    async def darhol() -> int:
        jurnal.append("boshlandi")
        return 1

    vazifa = asyncio.create_task(darhol())
    print(f"  eager_task_factory bilan yaratilishdayoq ishga tushdi: {jurnal == ['boshlandi']}")
    print(f"  darhol done(): {vazifa.done()}")
    await vazifa


def main() -> None:
    print("=== 1. Korutina obyekti ===")
    korutina = salom()
    print(f"  salom() natijasi: {type(korutina).__name__}, korutinami: {inspect.iscoroutine(korutina)}")
    print("  ⚠️ chaqirish bajarish emas — kod hali ishlamadi")
    print(f"  asyncio.run(korutina) → {asyncio.run(korutina)}")

    print("\n=== 2. Loopga kirish ===")
    asyncio.run(loop_haqida())
    try:
        asyncio.get_running_loop()
    except RuntimeError as xato:
        print(f"  loopdan tashqarida: RuntimeError: {xato}")

    print("\n=== 3. Task ===")
    asyncio.run(vazifa_haqida())

    print("\n=== 4. call_soon navbati ===")
    asyncio.run(navbat_tartibi())

    print("\n=== 5. sleep(0) va navbat ===")
    asyncio.run(navbat_almashish())

    print("\n=== 6. Eager task (3.12+) ===")
    asyncio.run(tezkor_vazifa())


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Korutina obyekti ===
  salom() natijasi: coroutine, korutinami: True
  ⚠️ chaqirish bajarish emas — kod hali ishlamadi
  asyncio.run(korutina) → 42

=== 2. Loopga kirish ===
  AbstractEventLoop merosxo'ri: True
  ishlayapti: True, debug rejimi: False
  loopdan tashqarida: RuntimeError: no running event loop

=== 3. Task ===
  turi: Task, Future merosxo'ri: True
  yaratilgan zahoti done(): False   ← hali boshlanmagan
  await dan keyin: natija=42, done=True, nom=salom-vazifa

=== 4. call_soon navbati ===
  ['korutina-davom-etdi', 'call_soon-1', 'call_soon-2']
  ⭐ korutina await gacha ishlaydi, keyin navbatdagilar

=== 5. sleep(0) va navbat ===
  ['a0', 'b0', 'a1', 'b1', 'a2', 'b2']
  ⭐ sleep(0) har safar navbatni almashtiradi

=== 6. Eager task (3.12+) ===
  eager_task_factory bilan yaratilishdayoq ishga tushdi: True
  darhol done(): True

Nima ko'rsatdi: 2.1, 2.2, 2.3, 2.7-bo'limlar.

Misol 2 — gather, TaskGroup, bekor qilish

python
"""gather tartibi; return_exceptions; TaskGroup va ExceptionGroup; as_completed; timeout; cancel va finally."""

import asyncio

JURNAL: list[str] = []


async def kut(sekund: float, nom: str) -> str:
    await asyncio.sleep(sekund)
    JURNAL.append(nom)
    return nom


async def yiqiladi(nom: str) -> str:
    await asyncio.sleep(0.01)
    raise ValueError(f"{nom} yiqildi")


async def gather_tartibi() -> None:
    JURNAL.clear()
    natijalar = await asyncio.gather(kut(0.03, "a"), kut(0.01, "b"), kut(0.02, "c"))
    print(f"  natijalar (kirish tartibida): {natijalar}")
    print(f"  tugash tartibi:               {JURNAL}")
    print("  ⭐ gather tartibni saqlaydi, garchi 'b' birinchi tugagan bo'lsa ham")


async def gather_xatolar() -> None:
    natijalar = await asyncio.gather(kut(0.01, "yaxshi"), yiqiladi("yomon"), return_exceptions=True)
    turlar = [type(n).__name__ if isinstance(n, BaseException) else n for n in natijalar]
    print(f"  return_exceptions=True → {turlar}")
    try:
        await asyncio.gather(kut(0.01, "yaxshi"), yiqiladi("yomon"))
    except ValueError as xato:
        print(f"  return_exceptions=False → ValueError: {xato}")


async def guruh() -> None:
    JURNAL.clear()
    try:
        async with asyncio.TaskGroup() as grp:
            grp.create_task(kut(0.5, "uzoq"))
            grp.create_task(yiqiladi("tez"))
    except* ValueError as guruh_xatosi:
        ichidagilar = [type(x).__name__ for x in guruh_xatosi.exceptions]
        print(f"  ushlandi: {type(guruh_xatosi).__name__} → {ichidagilar}")
    print(f"  ⭐ 'uzoq' vazifa bekor qilindi, jurnalga tushmadi: {JURNAL == []}")


async def tugash_tartibida() -> None:
    kelajaklar = [kut(0.03, "a"), kut(0.01, "b"), kut(0.02, "c")]
    tartib = [await k for k in asyncio.as_completed(kelajaklar)]
    print(f"  as_completed tartibi: {tartib}  ← eng tez tugagani birinchi")


async def muddatlar() -> None:
    try:
        async with asyncio.timeout(0.02):
            await kut(1.0, "juda-uzoq")
    except TimeoutError:
        print("  asyncio.timeout(0.02) → TimeoutError")
    try:
        await asyncio.wait_for(kut(1.0, "juda-uzoq"), timeout=0.02)
    except TimeoutError:
        print("  wait_for(timeout=0.02) → TimeoutError")
    print("  ⭐ 14.5-darsdagidan farqli: vazifa haqiqatan bekor qilinadi")


async def bekor_qilish() -> None:
    tozalandi: list[str] = []

    async def tozalovchi() -> None:
        try:
            await asyncio.sleep(10)
        except asyncio.CancelledError:
            tozalandi.append("CancelledError ushlandi")
            raise                                  # ⭐ qayta ko'tarish shart
        finally:
            tozalandi.append("finally bajarildi")

    vazifa = asyncio.create_task(tozalovchi())
    await asyncio.sleep(0)
    vazifa.cancel()
    try:
        await vazifa
    except asyncio.CancelledError:
        print("  await vazifa → CancelledError")
    print(f"  vazifa.cancelled(): {vazifa.cancelled()}")
    print(f"  tozalash bosqichlari: {tozalandi}")
    print(f"  ⚠️ CancelledError Exception emas: {not issubclass(asyncio.CancelledError, Exception)}")


async def main() -> None:
    print("=== 1. gather tartibi ===")
    await gather_tartibi()
    print("\n=== 2. gather va xatolar ===")
    await gather_xatolar()
    print("\n=== 3. TaskGroup ===")
    await guruh()
    print("\n=== 4. as_completed ===")
    await tugash_tartibida()
    print("\n=== 5. Muddatlar ===")
    await muddatlar()
    print("\n=== 6. Bekor qilish va tozalash ===")
    await bekor_qilish()


if __name__ == "__main__":
    asyncio.run(main())

Natijaning muhim qismi:

text
=== 1. gather tartibi ===
  natijalar (kirish tartibida): ['a', 'b', 'c']
  tugash tartibi:               ['b', 'c', 'a']
  ⭐ gather tartibni saqlaydi, garchi 'b' birinchi tugagan bo'lsa ham

=== 2. gather va xatolar ===
  return_exceptions=True → ['yaxshi', 'ValueError']
  return_exceptions=False → ValueError: yomon yiqildi

=== 3. TaskGroup ===
  ushlandi: ExceptionGroup → ['ValueError']
  ⭐ 'uzoq' vazifa bekor qilindi, jurnalga tushmadi: True

=== 4. as_completed ===
  as_completed tartibi: ['b', 'c', 'a']  ← eng tez tugagani birinchi

=== 5. Muddatlar ===
  asyncio.timeout(0.02) → TimeoutError
  wait_for(timeout=0.02) → TimeoutError
  ⭐ 14.5-darsdagidan farqli: vazifa haqiqatan bekor qilinadi

=== 6. Bekor qilish va tozalash ===
  await vazifa → CancelledError
  vazifa.cancelled(): True
  tozalash bosqichlari: ['CancelledError ushlandi', 'finally bajarildi']
  ⚠️ CancelledError Exception emas: True

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

Misol 3 — Loopni bloklash va yechimlari

python
"""Bloklovchi chaqiruv butun loopni to'xtatadi; to_thread va run_in_executor bilan yechim; hisob uchun jarayonlar puli."""

import asyncio
import concurrent.futures as cf
import time


def bloklovchi_ish(sekund: float) -> str:
    time.sleep(sekund)                    # ⚠️ asinxron emas
    return f"{sekund} s bloklovchi ish tugadi"


def ogir_hisob(n: int) -> int:
    return sum(i * i for i in range(n))


async def yengil_vazifa(nom: str, jurnal: list[str]) -> None:
    await asyncio.sleep(0.05)
    jurnal.append(nom)


async def blokli_variant() -> float:
    jurnal: list[str] = []
    bosh = time.perf_counter()
    vazifa = asyncio.create_task(yengil_vazifa("yengil", jurnal))
    bloklovchi_ish(0.4)                   # ⚠️ loop butunlay to'xtaydi
    await vazifa
    oraliq = time.perf_counter() - bosh
    print(f"  yengil vazifa 0.05 s lik edi, lekin jami: 0.4 s dan ko'p: {oraliq > 0.4}")
    print(f"  jurnal: {jurnal}")
    return oraliq


async def toza_variant() -> float:
    jurnal: list[str] = []
    bosh = time.perf_counter()
    vazifa = asyncio.create_task(yengil_vazifa("yengil", jurnal))
    xabar = await asyncio.to_thread(bloklovchi_ish, 0.4)
    await vazifa
    oraliq = time.perf_counter() - bosh
    print(f"  to_thread natijasi: {xabar}")
    print(f"  yengil vazifa kutmadi, jami 0.4 s atrofida: {oraliq < 0.55}")
    print(f"  jurnal: {jurnal}")
    return oraliq


async def parallel_bloklovchi() -> None:
    bosh = time.perf_counter()
    natijalar = await asyncio.gather(*(asyncio.to_thread(bloklovchi_ish, 0.2) for _ in range(5)))
    oraliq = time.perf_counter() - bosh
    print(f"  5 ta 0.2 s lik bloklovchi ish: {len(natijalar)} natija")
    print(f"  ketma-ket 1.0 s bo'lardi, parallel 0.5 s dan kam: {oraliq < 0.5}")


async def hisob_uchun_jarayonlar() -> None:
    loop = asyncio.get_running_loop()
    with cf.ProcessPoolExecutor(max_workers=4) as pul:
        natijalar = await asyncio.gather(
            *(loop.run_in_executor(pul, ogir_hisob, 2_000_000) for _ in range(4))
        )
    kutilgan = sum(i * i for i in range(2_000_000))
    print(f"  4 ta og'ir hisob jarayonlar pulida bajarildi: {len(natijalar) == 4}")
    print(f"  natijalar to'g'ri: {all(n == kutilgan for n in natijalar)}")
    print("  ⭐ sof Python hisob — iplar emas, jarayonlar (14.5-dars)")


async def main() -> None:
    print("=== 1. ⚠️ Bloklovchi chaqiruv ===")
    blokli = await blokli_variant()

    print("\n=== 2. ✅ to_thread bilan ===")
    toza = await toza_variant()

    print("\n=== 3. Ikkisining farqi ===")
    print(f"  bloklovchi variant sekinroq: {blokli > toza}")
    print("  ⭐ ish hajmi bir xil — farq faqat loop bo'shligida")

    print("\n=== 4. Ko'p bloklovchi ishni birga bajarish ===")
    await parallel_bloklovchi()

    print("\n=== 5. Og'ir hisob uchun jarayonlar puli ===")
    await hisob_uchun_jarayonlar()


if __name__ == "__main__":
    asyncio.run(main())

Natijaning muhim qismi:

text
=== 1. ⚠️ Bloklovchi chaqiruv ===
  yengil vazifa 0.05 s lik edi, lekin jami: 0.4 s dan ko'p: True
  jurnal: ['yengil']

=== 2. ✅ to_thread bilan ===
  to_thread natijasi: 0.4 s bloklovchi ish tugadi
  yengil vazifa kutmadi, jami 0.4 s atrofida: True
  jurnal: ['yengil']

=== 3. Ikkisining farqi ===
  bloklovchi variant sekinroq: True
  ⭐ ish hajmi bir xil — farq faqat loop bo'shligida

=== 4. Ko'p bloklovchi ishni birga bajarish ===
  5 ta 0.2 s lik bloklovchi ish: 5 natija
  ketma-ket 1.0 s bo'lardi, parallel 0.5 s dan kam: True

=== 5. Og'ir hisob uchun jarayonlar puli ===
  4 ta og'ir hisob jarayonlar pulida bajarildi: True
  natijalar to'g'ri: True
  ⭐ sof Python hisob — iplar emas, jarayonlar (14.5-dars)

Nima ko'rsatdi: 2.6-bo'lim.

Misol 4 — Amaliy: 500 ta ulanishli xizmat

Kirishdagi vaziyat: minglab ulanishni bitta jarayonda ushlab turish. Bu yerda tarmoqni taqlid qilamiz (asyncio.sleep — haqiqiy kutish bilan bir xil xulq), lekin qolgan hamma narsa haqiqiy: bir vaqtdagi so'rovlar chegarasi (semafor), muddat, qayta urinish, xatolarni yig'ish va xushmuomala to'xtatish.

python
"""Semafor bilan chegaralash; muddat va qayta urinish; TaskGroup bilan yig'ish; ketma-ket variant bilan solishtirish."""

import asyncio
import threading
import time
from dataclasses import dataclass, field

SOROVLAR = [f"/api/hujjat/{i}" for i in range(500)]
BIR_VAQTDA = 50
ASOSIY_IPLAR = threading.active_count()          # dastur boshidagi iplar soni


@dataclass(slots=True)
class Hisobot:
    muvaffaqiyat: list[str] = field(default_factory=list)
    xatolar: list[tuple[str, str]] = field(default_factory=list)
    urinishlar: int = 0
    faol: int = 0
    eng_kop_faol: int = 0


async def soro(yol: str) -> str:
    """Tarmoq so'rovini taqlid qiladi: kutish + aniqlangan nosozliklar."""
    raqam = int(yol.rsplit("/", 1)[1])
    await asyncio.sleep(0.002 + (raqam % 5) * 0.001)
    if raqam % 100 == 7:
        raise ConnectionError("ulanish uzildi")
    if raqam % 100 == 13:
        await asyncio.sleep(1.0)                 # muddatdan oshadi
    return f"javob:{raqam}"


async def ishonchli_soro(yol: str, semafor: asyncio.Semaphore, hisobot: Hisobot) -> None:
    async with semafor:                          # ⭐ bir vaqtda ko'pi bilan BIR_VAQTDA ta
        hisobot.faol += 1
        hisobot.eng_kop_faol = max(hisobot.eng_kop_faol, hisobot.faol)
        try:
            for urinish in range(1, 3):
                hisobot.urinishlar += 1
                try:
                    async with asyncio.timeout(0.5):
                        hisobot.muvaffaqiyat.append(await soro(yol))
                    return
                except TimeoutError:
                    hisobot.xatolar.append((yol, "muddat tugadi"))
                    return                       # takrorlansa yana osiladi
                except ConnectionError as xato:
                    if urinish == 2:
                        hisobot.xatolar.append((yol, str(xato)))
        finally:
            hisobot.faol -= 1


async def hammasini_bajar() -> tuple[Hisobot, float]:
    hisobot = Hisobot()
    semafor = asyncio.Semaphore(BIR_VAQTDA)
    bosh = time.perf_counter()
    async with asyncio.TaskGroup() as guruh:
        for yol in SOROVLAR:
            guruh.create_task(ishonchli_soro(yol, semafor, hisobot))
    return hisobot, time.perf_counter() - bosh


async def ketma_ket(nechta: int) -> float:
    bosh = time.perf_counter()
    for yol in SOROVLAR[:nechta]:
        try:
            async with asyncio.timeout(0.5):
                await soro(yol)
        except (TimeoutError, ConnectionError):
            pass
    return time.perf_counter() - bosh


async def main() -> None:
    print("=== 1. 500 so'rov, bitta ip ===")
    hisobot, vaqt = await hammasini_bajar()
    jami = len(hisobot.muvaffaqiyat) + len(hisobot.xatolar)
    print(f"  muvaffaqiyatli: {len(hisobot.muvaffaqiyat)}, xato: {len(hisobot.xatolar)}")
    print(f"  hammasi hisobga olindi: {jami == len(SOROVLAR)}")
    print(f"  qayta urinishlar bo'ldi: {hisobot.urinishlar > len(SOROVLAR)}")

    print("\n=== 2. Xatolar turi bo'yicha ===")
    turlar: dict[str, int] = {}
    for _, sabab in hisobot.xatolar:
        turlar[sabab] = turlar.get(sabab, 0) + 1
    for sabab, son in sorted(turlar.items()):
        print(f"  {sabab}: {son}")

    print("\n=== 3. Semafor chegarasi ===")
    print(f"  eng ko'p bir vaqtda faol so'rov: {hisobot.eng_kop_faol}")
    print(f"  chegaradan oshmadi: {hisobot.eng_kop_faol <= BIR_VAQTDA}")
    print(f"  bitta ipda bajarildi: {threading.active_count() == ASOSIY_IPLAR}")

    print("\n=== 4. Ketma-ket variant bilan solishtirish ===")
    ketma_ket_vaqti = await ketma_ket(100)
    taxminiy_500 = ketma_ket_vaqti * 5           # 100 so'rovdan 500 ga chiziqli o'sish
    print(f"  asinxron variant ketma-ketdan tez: {vaqt < taxminiy_500}")
    print(f"  farq kamida 3 barobar: {taxminiy_500 / vaqt >= 3}")

    print("\n=== 5. Qoidalar ===")
    print("  ✅ Semaphore — bir vaqtdagi so'rovlar soni cheklangan")
    print("  ✅ asyncio.timeout — osilib qolgan so'rov bekor qilinadi")
    print("  ✅ TaskGroup — hamma vazifa tugashi kafolatlangan")
    print("  ✅ hisobot — har so'rov natijasi qayd etiladi")


if __name__ == "__main__":
    asyncio.run(main())

Natijaning muhim qismi:

text
=== 1. 500 so'rov, bitta ip ===
  muvaffaqiyatli: 490, xato: 10
  hammasi hisobga olindi: True
  qayta urinishlar bo'ldi: True

=== 2. Xatolar turi bo'yicha ===
  muddat tugadi: 5
  ulanish uzildi: 5

=== 3. Semafor chegarasi ===
  eng ko'p bir vaqtda faol so'rov: 50
  chegaradan oshmadi: True
  bitta ipda bajarildi: True

=== 4. Ketma-ket variant bilan solishtirish ===
  asinxron variant ketma-ketdan tez: True
  farq kamida 3 barobar: True

=== 5. Qoidalar ===
  ✅ Semaphore — bir vaqtdagi so'rovlar soni cheklangan
  ✅ asyncio.timeout — osilib qolgan so'rov bekor qilinadi
  ✅ TaskGroup — hamma vazifa tugashi kafolatlangan
  ✅ hisobot — har so'rov natijasi qayd etiladi

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


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"asyncio — parallel bajarish" Bitta ipda konkurent bajarish; parallel emas
"async def chaqirilsa ishga tushadi" Faqat korutina obyekti hosil bo'ladi
"asyncio har doim tezroq" Faqat I/O kutish ko'p bo'lganda
"await kutishni bildiradi" Nazoratni loopga qaytarish nuqtasi
"Iplar kerak emas" Bloklovchi kutubxonalar uchun to_thread kerak
"CancelledError ni except Exception ushlaydi" Yo'q — u BaseException
"gather natijalarni tugash tartibida beradi" Kirish tartibida
"timeout vazifani to'xtatmaydi" asyncio da to'xtatadi (concurrent.futures dan farqli)

6. Keng tarqalgan xatolar va yechimlari

1. Loopda bloklovchi chaqiruv

python
time.sleep(1); requests.get(url)          # ❌ hamma vazifa to'xtaydi
await asyncio.sleep(1)                    # ✅
await asyncio.to_thread(requests.get, url)

2. await unutilgan

python
asyncio.sleep(1)                          # ❌ RuntimeWarning: never awaited
await asyncio.sleep(1)                    # ✅

3. Vazifaga havola saqlanmagan

python
asyncio.create_task(fon_ishi())           # ⚠️ yig'ib tashlanishi mumkin
VAZIFALAR.add(t := asyncio.create_task(fon_ishi()))
t.add_done_callback(VAZIFALAR.discard)    # ✅

4. CancelledError ni yutish

python
except asyncio.CancelledError:
    pass                                   # ❌ bekor qilish ishlamay qoladi
    raise                                  # ✅

5. Har so'rov uchun yangi sessiya

python
async with httpx.AsyncClient() as m:       # ❌ har so'rovda
SESSIYA = httpx.AsyncClient()              # ✅ bir marta

6. Chegarasiz vazifa yaratish

python
await asyncio.gather(*(soro(u) for u in 100_000 * [...]))   # ❌
async with asyncio.Semaphore(50):                           # ✅ chegara

7. asyncio.run ni tsikl ichida chaqirish

python
for x in malumot:
    asyncio.run(ishla(x))                  # ❌ har safar yangi loop
asyncio.run(main())                        # ✅ bitta kirish nuqtasi

8. Sinxron kod ichidan await

python
def oddiy():
    natija = await ishla()                 # ❌ SyntaxError
async def oddiy(): ...                     # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 14.1-dars (o'tilgan): korutina — eng arzon konkurentlik birligi
  • 14.5-dars (o'tilgan): run_in_executor orqali pullar bilan bog'lash
  • 14.7-dars: amaliy asinxron kod — mijoz, navbat, asinxron iterator
  • 14.8-dars: qaysi modelni qachon tanlash
  • 9.4-dars (o'tilgan): ExceptionGroup va except*
  • 17-qism: asinxron kodni testlash
  • 20-qism: FastAPI — asinxron veb-freymvork
  • 29-qism: minglab ulanishli xizmatlarni miqyoslash

8. Eng yaxshi amaliyotlar

  1. Kirish nuqtasi — bitta asyncio.run(main()).

  2. TaskGroup ni sukut tanlov qiling — u vazifalarni unutmaydi va xatoda qolganlarini bekor qiladi.

  3. Loopni hech qachon bloklamang. Bloklovchi kutubxona bo'lsa — to_thread.

  4. Har vazifaga muddat bering (asyncio.timeout).

  5. Bir vaqtdagi ishlar sonini Semaphore bilan cheklang.

  6. CancelledError ni qayta ko'taring, tozalashni finally da qiling.

  7. Fon vazifalariga havola saqlang.

  8. Ishlab chiqishda debug=True — sekin callback larni ko'rsatadi.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import asyncio, inspect
1.  async def f(): return 1
    print(type(f()).__name__)
2.  async def f(): return 1
    print(inspect.iscoroutinefunction(f))
3.  async def f(): return 1
    print(asyncio.run(f()))
4.  async def m():
        return type(asyncio.create_task(asyncio.sleep(0))).__name__
    print(asyncio.run(m()))
5.  async def m():
        t = asyncio.create_task(asyncio.sleep(0))
        d = t.done()
        await t
        return d, t.done()
    print(asyncio.run(m()))
6.  async def k(n): return n
    async def m(): return await asyncio.gather(k(3), k(1), k(2))
    print(asyncio.run(m()))
7.  print(issubclass(asyncio.CancelledError, Exception))
8.  print(issubclass(asyncio.CancelledError, BaseException))
9.  async def m():
        try:
            await asyncio.wait_for(asyncio.sleep(1), timeout=0.01)
        except TimeoutError:
            return "muddat"
    print(asyncio.run(m()))
10. async def m():
        t = asyncio.create_task(asyncio.sleep(1))
        await asyncio.sleep(0)
        t.cancel()
        try:
            await t
        except asyncio.CancelledError:
            return t.cancelled()
    print(asyncio.run(m()))
11. async def m(): return isinstance(asyncio.create_task(asyncio.sleep(0)), asyncio.Future)
    print(asyncio.run(m()))
12. try:
        asyncio.get_running_loop()
    except RuntimeError as e:
        print(type(e).__name__)
Javoblar
  1. coroutine
  2. True
  3. 1
  4. Task
  5. (False, True)
  6. [3, 1, 2] — kirish tartibida
  7. False — u BaseException
  8. True
  9. muddat
  10. True
  11. True — Task Future merosxo'ri
  12. RuntimeError

Vazifa 2: Xatolarni tuzating

python
1.  async def yukla(url):
        return requests.get(url).text

2.  async def main():
        asyncio.sleep(1)
        print("tugadi")

3.  async def main():
        for x in malumot:
            await ishla(x)                 # 10 000 element, har biri I/O

4.  async def ishchi():
        try:
            await uzoq_ish()
        except Exception:
            qayd("xato")

5.  for element in malumot:
        asyncio.run(ishla(element))
Javoblar
python
1.  async def yukla(mijoz, url):           # httpx.AsyncClient
        javob = await mijoz.get(url)
        return javob.text
    # yoki bloklovchi kutubxona majbur bo'lsa:
    # return (await asyncio.to_thread(requests.get, url)).text

2.  async def main():
        await asyncio.sleep(1)             # await qo'shildi
        print("tugadi")

3.  semafor = asyncio.Semaphore(50)
    async def chegarali(x):
        async with semafor:
            return await ishla(x)
    async with asyncio.TaskGroup() as g:
        for x in malumot:
            g.create_task(chegarali(x))

4.  async def ishchi():
        try:
            await uzoq_ish()
        except asyncio.CancelledError:
            raise                          # bekor qilishni yutmaymiz
        except Exception:
            qayd("xato")

5.  async def main():
        async with asyncio.TaskGroup() as g:
            for element in malumot:
                g.create_task(ishla(element))
    asyncio.run(main())                    # bitta loop

Vazifa 3: Mini hodisa sikli

asyncio siz, generatorlar asosida ishlaydigan soddalashtirilgan hodisa sikli yozing:

  1. yield ni "nazoratni qaytarish" nuqtasi sifatida ishlating
  2. sleep(t) ni taqlid qiluvchi vosita qo'shing (time.monotonic bilan)
  3. deque da tayyor vazifalar navbatini yuriting
  4. Uchta "korutina" ni birga bajarib, navbat almashishini ko'rsating
  5. asyncio bilan bir xil natija berishini tekshiring

Vazifa 4: Bloklash detektori

  1. Loopni kuzatuvchi vazifa yozing: har 10 ms da asyncio.sleep(0.01) qilib, haqiqiy o'tgan vaqtni o'lchasin
  2. Kechikish 50 ms dan oshsa — ogohlantirish chiqarsin
  3. Ataylab bloklovchi kod qo'shib, detektorni sinab ko'ring
  4. asyncio.run(main(), debug=True) ogohlantirishlari bilan solishtiring

Vazifa 5: Ishonchli asinxron mijoz

AsinxronMijoz yozing:

  1. Semafor bilan chegara, har so'rovga muddat
  2. Eksponensial kechikish bilan qayta urinish (0.1 · 2^n)
  3. Ketma-ket 5 xatodan keyin "zanjir uzgich" (circuit breaker) ochilsin
  4. Statistika: so'rovlar, qayta urinishlar, muvaffaqiyat foizi
  5. Xushmuomala to'xtatish: cancel da faol so'rovlar tugashini kutsin (shield)

Vazifa 6: Asinxron quvur

  1. Uch bosqich: olish (I/O) → tahlil (hisob) → saqlash (I/O)
  2. Bosqichlarni asyncio.Queue bilan ulang
  3. Hisob bosqichini run_in_executor ga bering
  4. Har bosqichda ishchilar sonini sozlanadigan qiling
  5. O'tkazuvchanlikni o'lchang va eng tor joyni toping

Vazifa 7: O'ylash

JavaScript'da hodisa sikli tilning o'zida — har bir funksiya asinxron muhitda ishlaydi. Go'da goroutine'lar runtime tomonidan rejalashtiriladi va "bloklovchi" chaqiruv avtomatik ravishda boshqa goroutine'ga yo'l beradi. Python'da esa async funksiyalar alohida "rang"ga ega: sinxron funksiyadan await qilib bo'lmaydi. Nega Python shu yo'lni tanlagan va bu qanday muammolarni keltirib chiqaradi?

Javob

Qisqa javob: Python asyncio ni kutubxona sifatida, mavjud tilga zarar bermay qo'shdi. Shuning uchun asinxronlik async/await kalit so'zlari bilan ko'rinadigan qilindi — bu "funksiya rangi" (function colouring) muammosini keltirib chiqaradi, lekin ayni paytda kod qayerda to'xtashini aniq ko'rsatadi.

1. Uch xil yondashuv

Til Model Rang muammosi
JavaScript Tug'ma hodisa sikli, Promise Bor, lekin butun ekotizim asinxron
Go Runtime M:N rejalashtiruvchi Yo'q — har chaqiruv "bloklashi" mumkin
Java 21+ Virtual iplar Yo'q — eski kod o'zgarmay ishlaydi
Python Kutubxona + til kalit so'zlari Bor va sezilarli

2. "Funksiya rangi" muammosi

  • async funksiyani faqat async funksiyadan await qilish mumkin
  • Sinxron kutubxona (masalan, requests, psycopg2) asinxron kodda ishlatilmaydi
  • Natijada ekotizim ikkiga bo'linadi: requests/httpx, psycopg2/asyncpg, redis/redis.asyncio
  • Kutubxona mualliflari ko'pincha ikkita variant yozishga majbur

3. Nega Python shunday qilgan

  1. Orqaga moslik: mavjud millionlab qator sinxron kod buzilmasligi kerak edi
  2. GIL va C kengaytmalari: Go'dagi kabi avtomatik rejalashtirish C stekini boshqarishni talab qiladi
  3. Aniqlik: await kod qayerda to'xtashini ko'rsatadi — bu poyga holatlarini kamaytiradi: await orasidagi kod atomar
  4. Kutubxona sifatida rivojlanish: asyncio tildan alohida yangilanishi mumkin

4. Aniqlikning foydasi

python
# Go/Java: har qatorda boshqa goroutine ishga tushishi mumkin
# Python: faqat await da
hisob = mijoz.balans          # bu yerda hech kim aralasha olmaydi
hisob -= summa                # atomar
await saqla(hisob)            # ⭐ faqat shu yerda navbat almashadi

Bu threading dagi ko'p poyga holatini asyncio'da yo'q qiladi (14.3-dars).

5. Kelajak

Python ham virtual iplarga o'xshash yechimlarni muhokama qilmoqda (greenlet, gevent allaqachon mavjud), lekin ular C kengaytmalari bilan mos kelmasligi mumkin. 3.13+ dagi erkin ipli (free-threaded) rejim boshqa yo'l: haqiqiy parallel iplar (12.6-dars).

6. Xulosa

  1. Python asinxronlikni kutubxona sifatida qo'shgani uchun async "rang" paydo bo'ldi
  2. Rang — bo'linish narxi, lekin await aniqligini beradi
  3. Aniqlik poyga holatlarini kamaytiradi — bu katta yutuq
  4. Go/Java yo'li qulayroq, lekin Python'ning C ekotizimi uchun mos emas

Nimani mustahkamlaydi: 2.1–2.8-bo'limlar.


Xulosa

Bu darsda hodisa siklining ichki mexanizmini ko'rdik.

Eng muhim uch fikr:

  1. Hodisa sikli — bitta ipda navbat bilan ishlaydigan rejalashtiruvchi. async def chaqirilganda kod bajarilmaydi — korutina obyekti hosil bo'ladi; uni await yoki create_task ishga tushiradi. await esa "kutish" emas, nazoratni loopga qaytarish nuqtasi: shu payt boshqa vazifalar ishlaydi. Shuning uchun minglab ulanish bitta ipda sig'adi — har biri ip emas, kichik obyekt.

  2. TaskGroup va timeout — zamonaviy tanlov. gather natijalarni kirish tartibida beradi, TaskGroup esa bittasi yiqilsa qolganlarini bekor qilib, xatolarni ExceptionGroup ga yig'adi. asyncio.timeout vazifani haqiqatan bekor qiladi — bu concurrent.futures dagi muddatdan muhim farq. CancelledError — BaseException, uni except Exception ushlamaydi va uni yutib yubormaslik kerak.

  3. Loopni bloklash — eng qimmat xato. Bitta time.sleep yoki requests.get butun serverni to'xtatadi: hamma vazifa kutadi. Yechim — asinxron kutubxona, bo'lmasa asyncio.to_thread (I/O uchun) yoki run_in_executor + jarayonlar puli (hisob uchun). Ishlab chiqishda debug=True shunday joylarni topib beradi.

Keyingi darsda bu mexanizmni amaliyotga ko'chiramiz: asinxron mijoz, asyncio.Queue bilan quvur, asinxron iterator va kontekst menejerlar.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
14.6-dars: asyncio — hodisa sikli — IlmHamroh