IlmHamroh
Python kursi/Konkurentlik va parallellik8/8-dars23 daqiqa
Mundarija (23)

14.8-dars: Qaysi birini qachon tanlash

14-QISM — KONKURENTLIK VA PARALLELLIK · 8-dars


1. Kirish va motivatsiya

14-qismda to'rtta model o'rgandik: iplar, jarayonlar, subinterpretatorlar va asyncio. Har biri o'z darsida yaxshi ko'rindi. Endi eng muhim savol: aniq vazifa uchun qaysi birini tanlash kerak?

Noto'g'ri tanlov qimmatga tushadi. Ikki real vaziyat:

  • Jamoa rasmlarni qayta ishlash xizmatini asyncio ga ko'chirdi, chunki "asinxron — tez". Natija: tezlik o'zgarmadi, lekin kod ikki barobar murakkablashdi. Sabab — ish CPU yuki edi, asyncio esa faqat kutishni tejaydi.
  • Boshqa jamoa 2 000 ta API so'rovi uchun ProcessPoolExecutor ishlatdi. Har so'rov jarayonlar orasida serializatsiya qilindi, 16 jarayon xotirani to'ldirdi. Oddiy ThreadPoolExecutor(50) bir xil ishni 5 barobar kam xotira bilan bajardi.

Tanlov "qaysi texnologiya zamonaviyroq" degan savolga emas, "vaqt qayerga ketyapti?" degan savolga bog'liq. Bu darsda shu savolga o'lchov bilan javob beramiz va 14-qismni yakunlaymiz.

Bu darsda:

  • Yuk turi: I/O, CPU yoki aralash — qanday aniqlanadi
  • To'rt model bir xil yukda: haqiqiy o'lchovlar
  • Yaratish narxi va miqyos: 1 000 ip va 10 000 vazifa
  • Amdahl qonuni: parallellikning chegarasi
  • Qaror daraxti va gibrid arxitekturalar
  • Free-threading va kelajak
  • Amaliy: yukni avtomatik tasniflovchi va gibrid quvur
  • 14-qism va 3-bosqich yakuni

2. Nazariya — chuqur tushuntirish

2.1. Birinchi savol: vaqt qayerga ketyapti

Yuk turi Belgisi Misollar
I/O yuki Dastur ko'pincha kutadi HTTP so'rov, baza, fayl, navbat
CPU yuki Protsessor to'la band Hisob, parsing, siqish, rasm
Aralash Kutish + hisob Yuklab olish + tahlil

Oddiy o'lchov: CPU vaqti / devor vaqti nisbati.

python
bosh_devor, bosh_cpu = time.perf_counter(), time.process_time()
ish()
nisbat = (time.process_time() - bosh_cpu) / (time.perf_counter() - bosh_devor)
Nisbat Xulosa
≈ 0 I/O yuki — kutish ustun
≈ 1 CPU yuki — bitta yadro to'la
Oraliqda Aralash — ikkala tomonni alohida ko'ring

2.2. To'rt model — taqqoslash

Iplar Jarayonlar Subinterpretatorlar asyncio
Parallel CPU (GIL bilan) (3.14)
I/O kutishni tejaydi
Yaratish narxi O'rtacha Yuqori O'rtacha Juda past
Xotira / birlik ~MB stek O'nlab MB Bir necha MB KB
Bir vaqtda sig'imi Yuzlab O'nlab O'nlab O'n minglab
Umumiy xotira (xavfli) (bitta ip)
Ma'lumot uzatish Bepul Pickle Pickle/cheklangan Bepul
Poyga xavfi Har joyda Kam Kam Faqat await da
Kutubxonalar mosligi Hammasi Pickle bo'ladiganlar C kengaytmalar cheklangan Asinxron kutubxonalar kerak

2.3. Qaror daraxti

Vaqt qayerga ketyapti?
│
├─ I/O kutish
│   ├─ Minglab ulanish / asinxron kutubxonalar bor  → asyncio
│   ├─ O'nlab-yuzlab vazifa / sinxron kutubxonalar  → ThreadPoolExecutor
│   └─ asyncio + bloklovchi kutubxona               → asyncio + to_thread
│
├─ CPU hisob (sof Python)
│   ├─ Vazifalar mustaqil, katta                    → ProcessPoolExecutor
│   ├─ Yengilroq izolyatsiya kerak (3.14)           → InterpreterPoolExecutor
│   ├─ NumPy / C kengaytma (GIL ni qo'yib yuboradi) → iplar ham yetadi
│   └─ Juda mayda vazifalar                         → avval vektorlash / paketlash
│
└─ Aralash
    └─ asyncio (I/O) + run_in_executor(ProcessPool) (CPU)

2.4. Amdahl qonuni

Dasturning parallel bo'lmaydigan qismi tezlashish chegarasini belgilaydi:

tezlashish(N) = 1 / ((1 - P) + P / N)
Parallel qism P 4 yadro 16 yadro Cheksiz yadro
50% 1.60× 1.88× 2×
90% 3.08× 6.40× 10×
99% 3.88× 13.91× 100×

Xulosa: yadrolar qo'shishdan oldin ketma-ket qismni qisqartiring. 10% ketma-ket kod bilan hech qachon 10 barobardan tezlasha olmaysiz.

2.5. Gibrid arxitekturalar

Naqsh Qachon
asyncio + to_thread Asinxron xizmat, bitta-ikkita bloklovchi kutubxona
asyncio + ProcessPoolExecutor Veb-xizmat og'ir hisob bilan
Jarayonlar × iplar Har yadroda bitta jarayon, ichida I/O iplari (Gunicorn + threads)
Jarayonlar × asyncio Har yadroda bitta loop (Uvicorn workers)
Navbat + ishchilar Uzoq vazifalar — alohida xizmatga (Celery, RQ — 29-qism)

Ishlab chiqarishdagi veb-serverlar deyarli har doim gibrid: jarayonlar — yadrolarni ishlatish uchun, iplar yoki asyncio — har jarayon ichida ulanishlar uchun.

2.6. Free-threading va kelajak

Yo'nalish Holati (3.14) Nimani o'zgartiradi
Free-threaded build (3.14t) Rasmiy qo'llab-quvvatlanadi Iplar CPU yukida ham parallel
Subinterpretatorlar InterpreterPoolExecutor Jarayondan yengil izolyatsiya
JIT Tajribaviy Bitta ip tezligi

Free-threaded rejimda iplar haqiqiy parallel bo'ladi — demak 14.3-darsdagi poyga holatlari yanada ko'p uchraydi. Qulflarsiz "tasodifan ishlagan" kod buzilishi mumkin.

2.7. Tanlov qoidalari

  1. Avval o'lchang — taxmin emas (process_time / perf_counter)
  2. Eng sodda modeldan boshlang — ko'p hollarda ketma-ket kod yetarli
  3. I/O → iplar yoki asyncio, CPU → jarayonlar
  4. Mayda vazifalarni paketlang (12.6, 14.5-darslar)
  5. Bir vaqtdagi ishlarni cheklang — Semaphore, max_workers, maxsize
  6. Algoritm va kutubxonani konkurentlikdan oldin yaxshilang

3. Tez ma'lumotnoma

python
# Yuk turini aniqlash
nisbat = cpu_vaqti / devor_vaqti          # ≈0 → I/O, ≈1 → CPU

# I/O, sinxron kutubxonalar
with ThreadPoolExecutor(max_workers=32) as ex: ...

# I/O, minglab ulanish
async with asyncio.TaskGroup() as g: ...

# CPU
with ProcessPoolExecutor() as ex: ex.map(f, data, chunksize=...)

# Aralash
await loop.run_in_executor(jarayonlar_puli, ogir_hisob, x)

Qoidalar

avval o'lchang: process_time / perf_counter
I/O → iplar yoki asyncio
CPU → jarayonlar (yoki subinterpretatorlar)
aralash → asyncio + ProcessPool
Amdahl: ketma-ket qism chegarani belgilaydi

4. Batafsil misollar

Misol 1 — I/O yukida to'rt model

python
"""Bir xil I/O yuki (40 ta 50 ms kutish): ketma-ket, iplar, jarayonlar, subinterpretatorlar, asyncio."""

import asyncio
import concurrent.futures as cf
import time

VAZIFALAR = 40
HA, YOQ = "✅ ha", "❌ yo'q"


def kutish(_: int) -> int:
    time.sleep(0.05)                     # tarmoq so'rovini taqlid qiladi
    return 1


async def kutish_async(_: int) -> int:
    await asyncio.sleep(0.05)
    return 1


def olchov(funksiya) -> float:
    bosh = time.perf_counter()
    funksiya()
    return time.perf_counter() - bosh


def pul_bilan(Ex, ishchilar: int) -> None:
    with Ex(max_workers=ishchilar) as ex:
        assert sum(ex.map(kutish, range(VAZIFALAR))) == VAZIFALAR


async def asinxron() -> None:
    assert sum(await asyncio.gather(*(kutish_async(i) for i in range(VAZIFALAR)))) == VAZIFALAR


def main() -> None:
    vaqtlar = {
        "ketma-ket": olchov(lambda: [kutish(i) for i in range(VAZIFALAR)]),
        "iplar (20)": olchov(lambda: pul_bilan(cf.ThreadPoolExecutor, 20)),
        "jarayonlar (8)": olchov(lambda: pul_bilan(cf.ProcessPoolExecutor, 8)),
        "subinterpretatorlar (8)": olchov(lambda: pul_bilan(cf.InterpreterPoolExecutor, 8)),
        "asyncio": olchov(lambda: asyncio.run(asinxron())),
    }
    asos = vaqtlar["ketma-ket"]

    print("=== 1. Kamida 1.5 barobar tezlashdimi ===")
    for nom, vaqt in vaqtlar.items():
        if nom != "ketma-ket":
            print(f"  {nom:24} {HA if asos / vaqt >= 1.5 else YOQ}")

    print("\n=== 2. Modellar orasida ===")
    print(f"  asyncio iplardan tez: {vaqtlar['asyncio'] < vaqtlar['iplar (20)']}")
    print(f"  iplar jarayonlardan tez: {vaqtlar['iplar (20)'] < vaqtlar['jarayonlar (8)']}")
    print(f"  asyncio ketma-ketdan kamida 10 barobar tez: {asos / vaqtlar['asyncio'] >= 10}")

    print("\n=== 3. Nega ===")
    print("  ⭐ hamma model kutishni tejaydi — GIL kutish paytida qo'yib yuboriladi")
    print("  jarayonlar: ishga tushirish narxi + faqat 8 ishchi")
    print("  iplar: 20 ishchi, lekin har biri OS ipi")
    print("  asyncio: 40 ta arzon vazifa, bitta ip")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Kamida 1.5 barobar tezlashdimi ===
  iplar (20)               ✅ ha
  jarayonlar (8)           ✅ ha
  subinterpretatorlar (8)  ✅ ha
  asyncio                  ✅ ha

=== 2. Modellar orasida ===
  asyncio iplardan tez: True
  iplar jarayonlardan tez: True
  asyncio ketma-ketdan kamida 10 barobar tez: True

=== 3. Nega ===
  ⭐ hamma model kutishni tejaydi — GIL kutish paytida qo'yib yuboriladi
  jarayonlar: ishga tushirish narxi + faqat 8 ishchi
  iplar: 20 ishchi, lekin har biri OS ipi
  asyncio: 40 ta arzon vazifa, bitta ip

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

Misol 2 — CPU yukida to'rt model

python
"""Bir xil CPU yuki (8 ta sof Python hisob): ketma-ket, iplar, jarayonlar, subinterpretatorlar, asyncio."""

import asyncio
import concurrent.futures as cf
import sys
import time

VAZIFALAR = 8
HA, YOQ = "✅ ha", "❌ yo'q"


def hisob(_: int = 0) -> int:
    s = 0
    for i in range(6_000_000):
        s += i * i
    return s


KUTILGAN = 0                             # main() da hisoblanadi — ishchilar qayta hisoblamasin


def olchov(funksiya) -> float:
    bosh = time.perf_counter()
    funksiya()
    return time.perf_counter() - bosh


def pul_bilan(Ex) -> None:
    with Ex(max_workers=VAZIFALAR) as ex:
        assert all(n == KUTILGAN for n in ex.map(hisob, range(VAZIFALAR)))


async def asinxron() -> None:
    async def vazifa() -> int:
        return hisob()                   # await yo'q — loop band
    natijalar = await asyncio.gather(*(vazifa() for _ in range(VAZIFALAR)))
    assert all(n == KUTILGAN for n in natijalar)


def main() -> None:
    global KUTILGAN
    KUTILGAN = hisob()
    print(f"GIL yoqilgan: {sys._is_gil_enabled()}")
    vaqtlar = {
        "ketma-ket": olchov(lambda: [hisob() for _ in range(VAZIFALAR)]),
        "iplar": olchov(lambda: pul_bilan(cf.ThreadPoolExecutor)),
        "jarayonlar": olchov(lambda: pul_bilan(cf.ProcessPoolExecutor)),
        "subinterpretatorlar": olchov(lambda: pul_bilan(cf.InterpreterPoolExecutor)),
        "asyncio": olchov(lambda: asyncio.run(asinxron())),
    }
    asos = vaqtlar["ketma-ket"]

    print("\n=== 1. Kamida 1.5 barobar tezlashdimi ===")
    for nom, vaqt in vaqtlar.items():
        if nom != "ketma-ket":
            print(f"  {nom:20} {HA if asos / vaqt >= 1.5 else YOQ}")

    print("\n=== 2. Tahlil ===")
    print(f"  iplar ketma-ketdan tez emas (GIL): {vaqtlar['iplar'] > asos * 0.7}")
    print(f"  asyncio ketma-ketdan tez emas: {vaqtlar['asyncio'] > asos * 0.7}")
    print(f"  jarayonlar va subinterpretatorlar parallel: "
          f"{asos / vaqtlar['jarayonlar'] >= 1.5 and asos / vaqtlar['subinterpretatorlar'] >= 1.5}")

    print("\n=== 3. Xulosa ===")
    print("  ⭐ I/O yukida g'olib bo'lgan asyncio bu yerda foydasiz")
    print("  ⭐ har interpretatorning o'z GIL i bor — shuning uchun subinterpretatorlar parallel")
    print("  ⚠️ free-threaded (3.14t) buildda iplar ham tezlashardi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
GIL yoqilgan: True

=== 1. Kamida 1.5 barobar tezlashdimi ===
  iplar                ❌ yo'q
  jarayonlar           ✅ ha
  subinterpretatorlar  ✅ ha
  asyncio              ❌ yo'q

=== 2. Tahlil ===
  iplar ketma-ketdan tez emas (GIL): True
  asyncio ketma-ketdan tez emas: True
  jarayonlar va subinterpretatorlar parallel: True

=== 3. Xulosa ===
  ⭐ I/O yukida g'olib bo'lgan asyncio bu yerda foydasiz
  ⭐ har interpretatorning o'z GIL i bor — shuning uchun subinterpretatorlar parallel
  ⚠️ free-threaded (3.14t) buildda iplar ham tezlashardi

Nima ko'rsatdi: 2.2, 2.6-bo'limlar.

Misol 3 — Yaratish narxi, miqyos va Amdahl

python
"""1 000 ip va 1 000 vazifa yaratish narxi; 10 000 bir vaqtdagi kutish; Amdahl qonuni jadvali."""

import asyncio
import threading
import time


def olchov(funksiya) -> float:
    bosh = time.perf_counter()
    funksiya()
    return time.perf_counter() - bosh


def ming_ip() -> None:
    iplar = [threading.Thread(target=time.sleep, args=(0,)) for _ in range(1_000)]
    for ip in iplar:
        ip.start()
    for ip in iplar:
        ip.join()


async def ming_vazifa() -> None:
    await asyncio.gather(*(asyncio.sleep(0) for _ in range(1_000)))


async def on_ming_kutish() -> None:
    await asyncio.gather(*(asyncio.sleep(0.2) for _ in range(10_000)))


def amdahl(parallel_qism: float, yadrolar: float) -> float:
    return 1 / ((1 - parallel_qism) + parallel_qism / yadrolar)


def main() -> None:
    print("=== 1. Yaratish narxi ===")
    ip_vaqti = olchov(ming_ip)
    vazifa_vaqti = olchov(lambda: asyncio.run(ming_vazifa()))
    print(f"  1 000 vazifa 1 000 ipdan kamida 5 barobar arzon: {ip_vaqti / vazifa_vaqti >= 5}")

    print("\n=== 2. 10 000 ta bir vaqtdagi kutish ===")
    vaqt = olchov(lambda: asyncio.run(on_ming_kutish()))
    print(f"  10 000 ta 0.2 s lik kutish 1 s dan kam vaqtda tugadi: {vaqt < 1.0}")
    print("  ⭐ 10 000 ip bilan bu OS chegaralariga urilardi")

    print("\n=== 3. Amdahl qonuni ===")
    print(f"  {'parallel qism':>14} {'4 yadro':>9} {'16 yadro':>9} {'1000 yadro':>11}")
    for p in (0.5, 0.9, 0.95, 0.99):
        qator = "".join(f"{amdahl(p, n):>9.2f}×" for n in (4, 16))
        print(f"  {p:>13.0%} {qator} {amdahl(p, 1000):>10.2f}×")

    print("\n=== 4. Amdahl nima deydi ===")
    print(f"  90% parallel kod, 16 yadro: {amdahl(0.9, 16):.2f}× — 16× emas")
    print(f"  ketma-ket qismni 10% dan 5% ga tushirish: {amdahl(0.9, 16):.2f}× → {amdahl(0.95, 16):.2f}×")
    print(f"  yadrolarni 16 dan 32 ga oshirish:         {amdahl(0.9, 16):.2f}× → {amdahl(0.9, 32):.2f}×")
    print("  ⭐ ketma-ket qismni qisqartirish yadro qo'shishdan foydaliroq")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Yaratish narxi ===
  1 000 vazifa 1 000 ipdan kamida 5 barobar arzon: True

=== 2. 10 000 ta bir vaqtdagi kutish ===
  10 000 ta 0.2 s lik kutish 1 s dan kam vaqtda tugadi: True
  ⭐ 10 000 ip bilan bu OS chegaralariga urilardi

=== 3. Amdahl qonuni ===
   parallel qism   4 yadro  16 yadro  1000 yadro
            50%      1.60×     1.88×       2.00×
            90%      3.08×     6.40×       9.91×
            95%      3.48×     9.14×      19.63×
            99%      3.88×    13.91×      90.99×

=== 4. Amdahl nima deydi ===
  90% parallel kod, 16 yadro: 6.40× — 16× emas
  ketma-ket qismni 10% dan 5% ga tushirish: 6.40× → 9.14×
  yadrolarni 16 dan 32 ga oshirish:         6.40× → 7.80×
  ⭐ ketma-ket qismni qisqartirish yadro qo'shishdan foydaliroq

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

Misol 4 — Amaliy: yukni tasniflovchi va gibrid quvur

Kirishdagi vaziyatlarning yechimi: avval yukni o'lchab tasniflaymiz, keyin aralash vazifani — "hujjatni yuklab olish (I/O) + tahlil qilish (CPU)" — uch xil arxitekturada bajaramiz: faqat iplar, faqat asyncio va gibrid (asyncio + jarayonlar puli).

python
"""CPU/devor nisbati bilan yuk turini aniqlash; aralash quvur: iplar, asyncio, asyncio + ProcessPool."""

import asyncio
import concurrent.futures as cf
import time

HUJJATLAR = 16


def yuk_turi(funksiya) -> str:
    bosh_devor, bosh_cpu = time.perf_counter(), time.process_time()
    funksiya()
    nisbat = (time.process_time() - bosh_cpu) / (time.perf_counter() - bosh_devor)
    if nisbat < 0.2:
        return "I/O"
    if nisbat > 0.7:
        return "CPU"
    return "aralash"


def tahlil(matn: str) -> int:
    """CPU qismi: sof Python hisob."""
    s = 0
    for i in range(1_200_000):
        s += (i * len(matn)) % 7
    return s


def yuklab_ol_sinxron(i: int) -> str:
    time.sleep(0.1)                      # I/O qismi
    return f"hujjat-{i}"


async def yuklab_ol(i: int) -> str:
    await asyncio.sleep(0.1)
    return f"hujjat-{i}"


def faqat_iplar() -> list[int]:
    def toliq(i: int) -> int:
        return tahlil(yuklab_ol_sinxron(i))
    with cf.ThreadPoolExecutor(max_workers=HUJJATLAR) as ex:
        return list(ex.map(toliq, range(HUJJATLAR)))


async def faqat_asyncio() -> list[int]:
    async def toliq(i: int) -> int:
        return tahlil(await yuklab_ol(i))          # ⚠️ hisob loopni bloklaydi
    return list(await asyncio.gather(*(toliq(i) for i in range(HUJJATLAR))))


async def gibrid() -> list[int]:
    loop = asyncio.get_running_loop()
    with cf.ProcessPoolExecutor(max_workers=8) as pul:
        async def toliq(i: int) -> int:
            matn = await yuklab_ol(i)                        # I/O — loopda
            return await loop.run_in_executor(pul, tahlil, matn)  # CPU — jarayonda
        return list(await asyncio.gather(*(toliq(i) for i in range(HUJJATLAR))))


def aralash_ish() -> None:
    for i in range(3):
        yuklab_ol_sinxron(i)
    tahlil("hujjat-0")


def olchov(funksiya) -> tuple[list[int], float]:
    bosh = time.perf_counter()
    natija = funksiya()
    return natija, time.perf_counter() - bosh


def main() -> None:
    print("=== 1. Yuk turini o'lchab aniqlash ===")
    print(f"  yuklab olish: {yuk_turi(lambda: [yuklab_ol_sinxron(i) for i in range(3)])}")
    print(f"  tahlil:       {yuk_turi(lambda: tahlil('hujjat-0'))}")
    print(f"  ikkalasi:     {yuk_turi(aralash_ish)}")

    print("\n=== 2. Aralash quvur, uch arxitektura ===")
    kutilgan = [tahlil(f"hujjat-{i}") for i in range(HUJJATLAR)]
    iplar, iplar_vaqti = olchov(faqat_iplar)
    asinx, asinx_vaqti = olchov(lambda: asyncio.run(faqat_asyncio()))
    gib, gib_vaqti = olchov(lambda: asyncio.run(gibrid()))
    print(f"  uchala natija to'g'ri: {iplar == kutilgan and asinx == kutilgan and gib == kutilgan}")
    print(f"  gibrid faqat iplardan tez: {gib_vaqti < iplar_vaqti}")
    print(f"  gibrid faqat asyncio dan tez: {gib_vaqti < asinx_vaqti}")
    print(f"  gibrid eng sekinidan kamida 2 barobar tez: {max(iplar_vaqti, asinx_vaqti) / gib_vaqti >= 2}")

    print("\n=== 3. Nega ===")
    print("  faqat iplar:   yuklash parallel, tahlil GIL da navbat kutadi")
    print("  faqat asyncio: tahlil loopni bloklaydi — yuklash ham to'xtaydi")
    print("  gibrid:        yuklash loopda, tahlil 8 ta jarayonda parallel")

    print("\n=== 4. Qaror ===")
    for vazifa, tanlov in [
        ("10 000 WebSocket ulanishi", "asyncio"),
        ("50 ta API ga sinxron kutubxona bilan", "ThreadPoolExecutor"),
        ("1 mln qatorni sof Python da hisoblash", "ProcessPoolExecutor"),
        ("veb-xizmat + og'ir hisobot", "asyncio + ProcessPoolExecutor"),
        ("NumPy matritsa amallari", "iplar yoki vektorlash (GIL qo'yib yuboriladi)"),
    ]:
        print(f"  {vazifa:40} → {tanlov}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Yuk turini o'lchab aniqlash ===
  yuklab olish: I/O
  tahlil:       CPU
  ikkalasi:     aralash

=== 2. Aralash quvur, uch arxitektura ===
  uchala natija to'g'ri: True
  gibrid faqat iplardan tez: True
  gibrid faqat asyncio dan tez: True
  gibrid eng sekinidan kamida 2 barobar tez: True

=== 3. Nega ===
  faqat iplar:   yuklash parallel, tahlil GIL da navbat kutadi
  faqat asyncio: tahlil loopni bloklaydi — yuklash ham to'xtaydi
  gibrid:        yuklash loopda, tahlil 8 ta jarayonda parallel

=== 4. Qaror ===
  10 000 WebSocket ulanishi                → asyncio
  50 ta API ga sinxron kutubxona bilan     → ThreadPoolExecutor
  1 mln qatorni sof Python da hisoblash    → ProcessPoolExecutor
  veb-xizmat + og'ir hisobot               → asyncio + ProcessPoolExecutor
  NumPy matritsa amallari                  → iplar yoki vektorlash (GIL qo'yib yuboriladi)

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


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

Noto'g'ri fikr To'g'risi
"asyncio har doim eng tez" Faqat I/O yukida; CPU yukida foydasiz
"Jarayonlar har doim tezlashtiradi" Mayda va I/O vazifalarda narxi foydadan oshadi
"Iplar Python'da befoyda" I/O yukida juda samarali
"Ko'proq ishchi — ko'proq tezlik" Amdahl va resurs chegaralari bor
"16 yadro — 16 barobar tezlik" Ketma-ket qism chegarani belgilaydi
"Bir model tanlash kerak" Real tizimlar gibrid
"Free-threading hamma muammoni hal qiladi" Poyga holatlarini ko'paytiradi
"Avval konkurentlik, keyin optimallashtirish" Avval o'lchov va algoritm

6. Keng tarqalgan xatolar va yechimlari

1. CPU yukini asyncio bilan tezlashtirishga urinish

python
async def ishla(x): return ogir_hisob(x)          # ❌ loop bloklanadi
await loop.run_in_executor(pul, ogir_hisob, x)    # ✅

2. I/O uchun jarayonlar

python
ProcessPoolExecutor().map(requests.get, urllar)   # ❌ ortiqcha narx
ThreadPoolExecutor(32).map(requests.get, urllar)  # ✅

3. O'lchamasdan tanlash

python
# ❌ "bu sekin, parallel qilamiz"
# ✅ process_time / perf_counter bilan yuk turini aniqlang

4. Chegarasiz ishchilar

python
ThreadPoolExecutor(max_workers=10_000)            # ❌

5. Mayda CPU vazifalarini alohida jarayonlarga yuborish

python
ex.map(kichik, katta_royxat)                      # ❌ chunksize=1
ex.map(kichik, katta_royxat, chunksize=10_000)    # ✅

6. Ketma-ket qismni e'tiborsiz qoldirish

python
# ❌ natijalarni yig'ish bitta ipda 50% vaqt oladi — yadro qo'shish yordam bermaydi

7. Modellarni aralashtirib, holatni bo'lishish

python
async def f(): await asyncio.to_thread(umumiy_royxat.append, x)  # ⚠️ iplar poygasi

8. NumPy uchun jarayonlar

python
ProcessPoolExecutor().map(np.dot, ...)            # ⚠️ katta massivlarni pickle qilish
# ✅ vektorlash yoki iplar — NumPy GIL ni qo'yib yuboradi

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 12.5–12.6-darslar (o'tilgan): GIL va uning ta'siri
  • 14.1–14.7-darslar (o'tilgan): to'rt modelning har biri
  • 18-qism: profiling — vaqt qayerga ketayotganini aniqlash
  • 22.5-dars: WSGI va ASGI — Uvicorn/Gunicorn ishchilari
  • 24-qism: NumPy va vektorlash
  • 29-qism: navbatlar, Celery, gorizontal miqyoslash
  • 30-qism: Rust/C kengaytmalari — GIL ni qo'yib yuboradigan kod

8. Eng yaxshi amaliyotlar

  1. Avval o'lchang: CPU/devor nisbati.

  2. Eng sodda yechimdan boshlang — ketma-ket kod ko'pincha yetadi.

  3. I/O → iplar yoki asyncio; CPU → jarayonlar.

  4. Aralash yukda — gibrid: asyncio + jarayonlar puli.

  5. Amdahl'ni yodda tuting — ketma-ket qismni qisqartiring.

  6. Ishchilar sonini cheklang va sozlanadigan qiling.

  7. Konkurentlikdan oldin algoritm va kutubxonani yaxshilang.

  8. Qaroringizni o'lchov bilan hujjatlashtiring — keyingi dasturchi nega shunday tanlanganini bilsin.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import asyncio, concurrent.futures as cf, sys, time
1.  def amdahl(p, n): return 1 / ((1 - p) + p / n)
    print(round(amdahl(0.5, 1_000_000), 2))
2.  def amdahl(p, n): return 1 / ((1 - p) + p / n)
    print(round(amdahl(1.0, 8), 2))
3.  def amdahl(p, n): return 1 / ((1 - p) + p / n)
    print(round(amdahl(0.0, 8), 2))
4.  print(hasattr(cf, "InterpreterPoolExecutor"))
5.  print(callable(getattr(sys, "_is_gil_enabled", None)))
6.  b = time.process_time()
    time.sleep(0.1)
    print(time.process_time() - b < 0.05)
7.  print(issubclass(cf.ThreadPoolExecutor, cf.Executor),
          issubclass(cf.ProcessPoolExecutor, cf.Executor))
8.  async def m(): return await asyncio.to_thread(sum, [1, 2, 3])
    print(asyncio.run(m()))
9.  async def m():
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, pow, 2, 10)
    print(asyncio.run(m()))
10. with cf.ThreadPoolExecutor(4) as ex:
        print(list(ex.map(len, ["a", "bb", "ccc"])))
11. def amdahl(p, n): return 1 / ((1 - p) + p / n)
    print(amdahl(0.9, 16) < amdahl(0.95, 16))
12. print(issubclass(cf.InterpreterPoolExecutor, cf.ThreadPoolExecutor))
Javoblar
  1. 2.0 — 50% ketma-ket qism chegarani 2× qiladi
  2. 8.0
  3. 1.0
  4. True — 3.14
  5. True
  6. True — sleep CPU vaqtini sarflamaydi
  7. True True
  8. 6
  9. 1024 — None — sukut iplar puli
  10. [1, 2, 3]
  11. True
  12. True — u iplar puli ustiga qurilgan, har ip o'z interpretatorida

Vazifa 2: Model tanlang va asoslang

Har vaziyat uchun model tanlang va bir jumla bilan asoslang:

1.  Telegram bot: 20 000 foydalanuvchi, har xabarga bazadan o'qish
2.  5 GB CSV faylni qatorma-qator sof Python bilan tozalash
3.  300 ta saytni har 5 daqiqada tekshiradigan monitoring (requests kutubxonasi)
4.  Video fayllarni ffmpeg bilan qayta kodlash (tashqi dastur)
5.  FastAPI xizmati: har so'rovda 2 soniyalik PDF hisobot yaratish
Javoblar
1.  asyncio — minglab bir vaqtdagi kutish, asinxron baza drayveri (asyncpg)
2.  ProcessPoolExecutor — sof Python CPU yuki; faylni bo'laklarga bo'lib, chunksize bilan
3.  ThreadPoolExecutor(32–64) — I/O yuki, sinxron kutubxona; yoki httpx + asyncio
4.  asyncio.create_subprocess_exec (yoki iplar) — og'ir ishni ffmpeg jarayoni bajaradi,
    Python faqat kutadi; parallel sonini Semaphore bilan yadrolar soniga cheklang
5.  asyncio + run_in_executor(ProcessPoolExecutor) — loop bloklanmasin;
    yuk katta bo'lsa — navbat + alohida ishchilar (Celery, 29-qism)

Vazifa 3: Avtomatik maslahatchi

maslahat(funksiya, namuna_argumentlar) funksiyasini yozing:

  1. Namunada CPU/devor nisbatini o'lchasin
  2. Bitta chaqiruv vaqtini o'lchasin (mayda/o'rtacha/og'ir)
  3. Argumentlar pickle bo'ladimi — tekshirsin
  4. Model, max_workers va chunksize tavsiya qilsin
  5. Tavsiyani haqiqiy o'lchov bilan tekshirib, jadval chiqarsin

Vazifa 4: Amdahl'ni amalda tekshirish

  1. 80% parallellashadigan, 20% ketma-ket vazifa yozing
  2. 1, 2, 4, 8 jarayonda o'lchang
  3. Haqiqiy tezlashishni Amdahl bashorati bilan bir grafikda solishtiring
  4. Farq sabablarini (ishga tushirish, pickle, xotira) tushuntiring

Vazifa 5: Gibrid veb-xizmat

asyncio stream lari bilan (14.7-dars) server yozing:

  1. /salom — darhol javob
  2. /hisobot — 1 soniyalik CPU hisob ProcessPoolExecutor da
  3. 200 ta bir vaqtdagi /salom so'rovi /hisobot bajarilayotganda ham tez javob olishini isbotlang
  4. Xuddi shuni hisobni loopda bajarib takrorlang va farqni o'lchang

Vazifa 6: Free-threading tajribasi

  1. Agar mavjud bo'lsa, python3.14t o'rnating
  2. Misol 2 ni ikkala buildda ishga tushiring
  3. Iplar natijasini solishtiring
  4. 14.3-darsdagi qulfsiz hisoblagichni ikkala buildda sinang — nima o'zgardi?
  5. Bitta ip tezligi qanchaga pasaydi?

Vazifa 7: O'ylash

Go tilida bitta model bor — goroutine'lar, va ular I/O ham, CPU ham uchun ishlaydi. Python'da esa to'rtta model va ularni tanlash qoidalari bor. Bu Python'ning kamchiligimi yoki afzalligimi? Free-threading va subinterpretatorlar kelajakda bu manzarani qanday o'zgartirishi mumkin?

Javob

Qisqa javob: Bu asosan tarixiy narx — GIL va C ekotizimi bilan moslik uchun to'langan. Ko'p model tanlash murakkabligini beradi, lekin har biri o'z joyida juda yaxshi ishlaydi. Free-threading va subinterpretatorlar Python'ni "bitta universal model"ga yaqinlashtirmoqda, lekin asyncio I/O uchun alohida qolishi ehtimoli katta.

1. Go qanday qiladi

Xususiyat Go
Birlik Goroutine (~2 KB stek, o'sadi)
Rejalashtirish Runtime M:N — goroutine'lar OS iplariga taqsimlanadi
I/O Runtime avtomatik "to'xtatib turadi" — bloklovchi ko'rinadigan kod asinxron ishlaydi
CPU Barcha yadrolarda parallel
Narx Umumiy xotira poygalari; runtime murakkabligi

2. Nega Python'da bunday emas

  1. GIL (12.5-dars): CPython xotira boshqaruvini sodda va tez qilish uchun tanlangan — natijada iplar CPU uchun parallel emas
  2. C kengaytmalari: NumPy, lxml va boshqalar GIL ga tayanadi; runtime ularni "to'xtatib" qo'ya olmaydi
  3. Orqaga moslik: mavjud kodni buzmasdan yangi model faqat qo'shilishi mumkin edi
  4. Shuning uchun har muammoga alohida vosita paydo bo'ldi: jarayonlar (CPU), asyncio (ko'p I/O), iplar (sinxron I/O)

3. Kamchilik tomoni

  • O'rganish egri chizig'i tik: to'rt model, to'rt xil xato turi
  • "Funksiya rangi" (14.6-dars) — ekotizim bo'linishi
  • Noto'g'ri tanlov oson (kirishdagi vaziyatlar)

4. Afzallik tomoni

  • Har model aniq kelishuvlarga ega: asyncio da poyga faqat await da (14.7-dars), jarayonlarda umumiy xotira yo'q
  • Jarayon izolyatsiyasi — bitta ishchi yiqilsa, boshqalar ishlaydi
  • Mavjud C ekotizimi to'liq saqlanadi
  • asyncio bitta ipda o'n minglab ulanishni kamroq xotira bilan ushlaydi

5. Kelajak

Yo'nalish Ta'siri
Free-threading (PEP 703) Iplar CPU uchun ham parallel — "iplar va jarayonlar" tanlovi yo'qolishi mumkin
Subinterpretatorlar (PEP 734) Bir jarayonda izolyatsiyali parallellik — multiprocessing o'rnini qisman egallaydi
asyncio I/O ko'p ulanishlar uchun qoladi — Go'da ham epoll ichkarida ishlaydi

Ehtimoliy manzara: iplar (free-threaded) — umumiy parallellik, asyncio — ko'p ulanishli I/O, jarayonlar — izolyatsiya va ishonchlilik uchun.

6. Xulosa

  1. To'rt model — GIL va C ekotizimi bilan moslikning tarixiy narxi
  2. Tanlash qiyin, lekin har model o'z joyida kuchli va aniq kafolatlarga ega
  3. Free-threading va subinterpretatorlar tanlovni soddalashtiradi
  4. Qoida o'zgarmaydi: vaqt qayerga ketayotganini o'lchang, keyin tanlang

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda konkurentlik modellarini tanlash qoidalarini o'lchov bilan asosladik.

Eng muhim uch fikr:

  1. Tanlov yuk turiga bog'liq — va uni o'lchash mumkin. CPU vaqti / devor vaqti nisbati ≈ 0 bo'lsa — I/O yuki, ≈ 1 bo'lsa — CPU yuki. I/O yukida asyncio va iplar ketma-ket koddan o'nlab barobar tezlashdi, CPU yukida esa ular hech narsa bermadi — faqat jarayonlar va subinterpretatorlar (har birining o'z GIL i bor) parallel ishladi.

  2. Real tizimlar gibrid. Aralash vazifada faqat iplar (hisob GIL da navbat kutadi) ham, faqat asyncio (hisob loopni bloklaydi) ham sekin — asyncio + run_in_executor(ProcessPoolExecutor) ikkalasidan tez bo'ldi. Ishlab chiqarishdagi serverlar ham shunday: yadrolar uchun jarayonlar, har jarayon ichida ulanishlar uchun iplar yoki asyncio.

  3. Amdahl qonuni — parallellikning shipi. 90% parallel kod 16 yadroda atigi 6.4× tezlashadi. Ketma-ket qismni qisqartirish yadro qo'shishdan foydaliroq. Shuning uchun tartib doim bir xil: o'lchash → algoritm va kutubxona → keyin konkurentlik.

14-qism yakuni

Dars Mavzu Asosiy g'oya
14.1 Jarayon, oqim, korutina Uch birlikning narxi va kafolatlari
14.2 threading asoslari Thread, join, daemon; ThreadPoolExecutor
14.3 Race condition, Lock, Queue O'qish-o'zgartirish-yozish poygasi; deadlock; navbat bilan ishchilar
14.4 multiprocessing spawn, pickle, __main__ himoyasi; Queue, Pipe, shared_memory
14.5 Pool va concurrent.futures Future, as_completed; chunksize, initializer; muddat va bekor qilish
14.6 asyncio — hodisa sikli Korutina va Task; TaskGroup; bekor qilish; loopni bloklash
14.7 async / await amaliyoti Asinxron iterator va kontekst menejer; Queue; await dagi poyga; stream lar
14.8 Qaysi birini tanlash Yuk turini o'lchash; qaror daraxti; gibrid; Amdahl

3-bosqich yakuni: "Python ichidan"

14-qism bilan kursning uchinchi bosqichi tugadi. 35 darsda Python'ni tashqaridan emas, ichidan ko'rdik:

Qism Nimani tushunib oldik
11 — Obyekt modeli va xotira Havolalar, reference counting, GC, interning, __slots__, weakref
12 — CPython ichki tuzilishi Bytecode, frame lar, PyObject, GIL, 3.11+ tezlashuvlari
13 — Turlar tizimi Annotatsiyalar, generiklar, Protocol, mypy, pydantic, turlar bilan dizayn
14 — Konkurentlik va parallellik Iplar, jarayonlar, subinterpretatorlar, asyncio va ularni tanlash

Endi siz Python kodini yozibgina qolmay, nega shunday ishlashini tushuntira olasiz: nega is ba'zan True, nega iplar hisobni tezlashtirmaydi, nega await dan keyin holat o'zgarishi mumkin.

Keyingi bosqich — "Kutubxona va professional vositalar". 15-qismda standart kutubxonaning eng ko'p ishlatiladigan modullaridan boshlaymiz: muntazam ifodalar, datetime, collections, itertools, pathlib, subprocess, logging — professional dasturchining kundalik asboblari.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
14.8-dars: Qaysi birini qachon tanlash — IlmHamroh