IlmHamroh
Python kursi/Konkurentlik va parallellik1/8-dars22 daqiqa
Mundarija (21)

14.1-dars: Jarayon, oqim, korutina farqi

14-QISM — KONKURENTLIK VA PARALLELLIK · 1-dars


1. Kirish va motivatsiya

12.5–12.6-darslarda GIL ni o'rgandik va "iplar, jarayonlar, asyncio" degan so'zlar bir necha marta uchradi. Endi ularni tizimli ko'rib chiqamiz — chunki noto'g'ri tanlov qimmatga tushadi.

Real vaziyat. Xabarnoma servisi 5 000 ta qurilmaga push-xabar yuborishi kerak edi. Har yuborish — 200 ms lik tarmoq so'rovi. Birinchi versiya ketma-ket ishladi: 5 000 × 0.2 s ≈ 17 daqiqa. Jamoa har xabar uchun bitta ip yaratdi — 5 000 ip. Server 1.2 GB xotira yedi, kontekst almashinuvi tufayli sekinlashdi va ba'zi iplar "ochlik"da qoldi.

To'g'ri yechim — korutinalar: 5 000 ta korutina ~4 MB xotira oladi va bitta ipda ishlaydi; yuborish 25 soniyada tugadi.

Uch model — uch xil narx va uch xil kafolat. Ularni farqlamasdan "konkurentlik" haqida gapirib bo'lmaydi.

Bu darsda:

  • Jarayon, ip (thread) va korutina: nima farqi bor
  • Yaratish narxi, xotira va miqyos: o'lchovlar bilan
  • Rejalashtirish: majburiy (preemptive) va hamkorlikdagi (cooperative)
  • Izolyatsiya va ma'lumot almashish
  • Konkurentlik va parallellik farqi
  • Har modelning identifikatorlari: PID, ip ID, vazifa nomi
  • Amaliy: ish turiga qarab model tanlash

2. Nazariya — chuqur tushuntirish

2.1. Uch model

Jarayon Ip (thread) Korutina
Kim boshqaradi Operatsion tizim Operatsion tizim Python (hodisa sikli)
Xotira O'z manzil fazosi Umumiy (jarayon ichida) Umumiy (ip ichida)
Yaratish narxi Eng qimmat (ms) O'rtacha (~0.1 ms) Eng arzon (~µs)
Bittasiga xotira MB lar ~50 KB – 8 MB (stek) ~1 KB
Almashinuv Qimmat (MMU, kesh) O'rtacha (yadro) Juda arzon (funksiya chaqiruvi kabi)
Parallellik (CPU) To'liq GIL bilan yo'q Yo'q
Nosozlik izolyatsiyasi Yiqilsa boshqalar ishlaydi Butun jarayon yiqiladi Butun jarayon
Ma'lumot almashish pickle, navbat, umumiy xotira To'g'ridan-to'g'ri (qulf kerak) To'g'ridan-to'g'ri (qulf odatda kerak emas)
Python'da multiprocessing threading asyncio

2.2. Konkurentlik va parallellik

Ma'nosi Misol
Konkurentlik Ko'p ishni navbat bilan olib borish Bitta oshpaz uch taomni almashib pishiradi
Parallellik Ko'p ishni bir vaqtda bajarish Uch oshpaz uch taomni birga pishiradi
  • asyncio — konkurentlik, parallellik emas (bitta ip)
  • Iplar — Python kodi uchun konkurentlik, I/O va GIL bo'shatilganda parallellik
  • Jarayonlar — haqiqiy parallellik

Ko'pincha kerak bo'lgani — konkurentlik: kutish vaqtini foydali ishga aylantirish.

2.3. Rejalashtirish: majburiy va hamkorlikdagi

Iplar — majburiy (preemptive): OT istalgan paytda ipni to'xtatib, boshqasiga o'tadi (Python'da GIL almashinuvi bilan birga — 12.5-dars).

python
def ishchi():
    while True:
        hisobla()          # ip o'zi hech narsa qilmasa ham almashadi

Korutinalar — hamkorlikdagi (cooperative): boshqaruv faqat await da uzatiladi.

python
async def ishchi():
    while True:
        hisobla()          # ⚠️ await yo'q — butun sikl to'xtaydi
        await asyncio.sleep(0)   # ✅ boshqalarga navbat berish
Iplar Korutinalar
Almashinuv nuqtalari Oldindan bilib bo'lmaydi Faqat await
Poyga holatlari Ko'p (12.5-dars) Kamroq, lekin bor
Bloklovchi kod Boshqa iplarga xalaqit bermaydi Butun siklni to'xtatadi
Debug Qiyinroq Osonroq (almashinuv ko'rinadi)

2.4. Izolyatsiya va xavfsizlik

Model Yiqilish ta'siri Umumiy holat
Jarayon Faqat o'zi yiqiladi Yo'q (aniq uzatish kerak)
Ip Butun jarayon yiqilishi mumkin (C kengaytma xatosi) Bor — qulf kerak
Korutina Istisno vazifada qoladi, lekin sikl davom etadi Bor — lekin await orasida almashinuv yo'q

Shuning uchun ishonchsiz kod yoki C kutubxonalari uchun jarayon xavfsizroq.

2.5. Identifikatorlar

python
os.getpid()                       # jarayon
threading.get_ident()             # ip (OT ID si emas, Python ID si)
threading.current_thread().name   # ip nomi
asyncio.current_task().get_name() # vazifa nomi

Loglarda shu uchtasini chiqarish — konkurent tizimni tushunishning eng arzon usuli.

2.6. Miqyos: nechtasi mumkin

Model Amaliy chegara Nima cheklaydi
Jarayon Yadrolar soni × 1–2 Xotira, kontekst almashinuvi
Ip Bir necha yuz – bir necha ming Stek xotirasi, yadro resurslari
Korutina Yuz minglab Faqat xotira (~1 KB)

"Har ulanish uchun bitta ip" — 1990-yillar modeli; bugun 10 000 ulanish uchun korutinalar ishlatiladi.

2.7. Qaysi birini qachon (qisqacha)

Ish Model
Ko'p tarmoq so'rovi, ulanishlar minglab asyncio
I/O, lekin kutubxona bloklovchi (requests, DB drayveri) Iplar
Sof Python CPU hisob Jarayonlar
C kutubxona GIL ni bo'shatadi (NumPy, hashlib) Iplar
Ishonchsiz yoki yiqiluvchan kod Jarayonlar

Batafsil — 14.8-darsda.


3. Tez ma'lumotnoma

python
import threading, multiprocessing, asyncio

threading.Thread(target=f).start()                 # ip
multiprocessing.Process(target=f).start()          # jarayon
asyncio.run(main())                                # korutinalar sikli

os.getpid()  threading.get_ident()  asyncio.current_task().get_name()

Taqqoslash

jarayon:  izolyatsiya, parallellik, qimmat
ip:       umumiy xotira, GIL, qulflar kerak
korutina: eng arzon, bitta ip, await da almashadi
konkurentlik ≠ parallellik

4. Batafsil misollar

Misol 1 — Uch model, bitta vazifa

python
"""Bir xil 'so'rovlar' uch model bilan: ketma-ket, iplar, korutinalar, jarayonlar; identifikatorlar va vaqt."""

import asyncio
import multiprocessing as mp
import os
import threading
import time

SOROVLAR = 12
KUTISH = 0.1


def sorov(nomer: int) -> tuple[int, int, str]:
    """Bloklovchi 'tarmoq so'rovi'."""
    time.sleep(KUTISH)
    return nomer, os.getpid(), threading.current_thread().name


async def async_sorov(nomer: int) -> tuple[int, int, str]:
    await asyncio.sleep(KUTISH)
    vazifa = asyncio.current_task()
    return nomer, os.getpid(), vazifa.get_name() if vazifa else "?"


def ketma_ket() -> list[tuple[int, int, str]]:
    return [sorov(i) for i in range(SOROVLAR)]


def iplar_bilan() -> list[tuple[int, int, str]]:
    natijalar: list[tuple[int, int, str]] = []
    qulf = threading.Lock()

    def ishchi(n: int) -> None:
        natija = sorov(n)
        with qulf:
            natijalar.append(natija)

    iplar = [threading.Thread(target=ishchi, args=(i,), name=f"ip-{i}") for i in range(SOROVLAR)]
    for ip in iplar:
        ip.start()
    for ip in iplar:
        ip.join()
    return natijalar


def korutinalar_bilan() -> list[tuple[int, int, str]]:
    async def hammasi() -> list[tuple[int, int, str]]:
        vazifalar = [asyncio.create_task(async_sorov(i), name=f"vazifa-{i}") for i in range(SOROVLAR)]
        return list(await asyncio.gather(*vazifalar))
    return asyncio.run(hammasi())


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


if __name__ == "__main__":
    print("=== 1. Asosiy jarayon ===")
    print(f"  PID: {os.getpid() > 0}, asosiy ip: {threading.current_thread().name}, yadrolar: {os.cpu_count() >= 1}")

    print("\n=== 2. Uch model ===")
    t_ket, n_ket = olch(ketma_ket)
    t_ip, n_ip = olch(iplar_bilan)
    t_as, n_as = olch(korutinalar_bilan)
    print(f"  ketma-ket  {SOROVLAR} × {KUTISH} s kutish → kamida {SOROVLAR * KUTISH:.1f} s: {t_ket >= SOROVLAR * KUTISH * 0.95}")
    print(f"  iplar      kamida 5 barobar tez: {t_ket / t_ip >= 5}")
    print(f"  korutinalar kamida 5 barobar tez: {t_ket / t_as >= 5}")
    print(f"  natijalar soni bir xil: {len(n_ket) == len(n_ip) == len(n_as) == SOROVLAR}")

    print("\n=== 3. Kim qayerda ishladi ===")
    print(f"  ketma-ket:   hammasi bitta ipda: {len({n for _, _, n in n_ket}) == 1}")
    print(f"  iplar:       {len({n for _, _, n in n_ip})} xil ip nomi")
    print(f"  korutinalar: {len({n for _, _, n in n_as})} xil vazifa nomi, hammasi bitta jarayonda: "
          f"{len({p for _, p, _ in n_as}) == 1}")
    print(f"  korutina nomlari: {sorted(n for _, _, n in n_as)[:3]} ...")

    print("\n=== 4. Jarayonlar ===")
    with mp.Pool(processes=4) as pul:
        bosh = time.perf_counter()
        n_jar = pul.map(sorov, range(SOROVLAR))
        t_jar = time.perf_counter() - bosh
    pidlar = {p for _, p, _ in n_jar}
    print(f"  {len(pidlar)} xil PID da bajarildi, asosiy jarayondan farq qiladi: {os.getpid() not in pidlar}")
    print(f"  ketma-ketdan tez: {t_ket / t_jar >= 2}")
    print("  ⭐ I/O uchun jarayonlar ortiqcha: iplar va korutinalar arzonroq")

Natijaning muhim qismi:

text
=== 1. Asosiy jarayon ===
  PID: True, asosiy ip: MainThread, yadrolar: True

=== 2. Uch model ===
  ketma-ket  12 × 0.1 s kutish → kamida 1.2 s: True
  iplar      kamida 5 barobar tez: True
  korutinalar kamida 5 barobar tez: True
  natijalar soni bir xil: True

=== 3. Kim qayerda ishladi ===
  ketma-ket:   hammasi bitta ipda: True
  iplar:       12 xil ip nomi
  korutinalar: 12 xil vazifa nomi, hammasi bitta jarayonda: True
  korutina nomlari: ['vazifa-0', 'vazifa-1', 'vazifa-10'] ...

=== 4. Jarayonlar ===
  4 xil PID da bajarildi, asosiy jarayondan farq qiladi: True
  ketma-ketdan tez: True
  ⭐ I/O uchun jarayonlar ortiqcha: iplar va korutinalar arzonroq

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

Misol 2 — Narx va miqyos

python
"""Yaratish vaqti; bitta birlikka xotira; 10 000 korutina va 10 000 ip farqi; amaliy chegaralar."""

import asyncio
import gc
import multiprocessing as mp
import sys
import threading
import time
import tracemalloc


def hech_narsa() -> None:
    pass


async def async_hech_narsa() -> None:
    await asyncio.sleep(0)


def iplar_yarat(n: int) -> None:
    iplar = [threading.Thread(target=hech_narsa) for _ in range(n)]
    for ip in iplar:
        ip.start()
    for ip in iplar:
        ip.join()


def korutinalar_yarat(n: int) -> None:
    async def hammasi() -> None:
        await asyncio.gather(*(async_hech_narsa() for _ in range(n)))
    asyncio.run(hammasi())


def jarayonlar_yarat(n: int) -> None:
    jarayonlar = [mp.Process(target=hech_narsa) for _ in range(n)]
    for j in jarayonlar:
        j.start()
    for j in jarayonlar:
        j.join()


def olch(funksiya, n: int, takror: int = 3) -> float:
    eng = float("inf")
    for _ in range(takror):
        bosh = time.perf_counter()
        funksiya(n)
        eng = min(eng, time.perf_counter() - bosh)
    return eng


if __name__ == "__main__":
    print("=== 1. 100 birlik yaratish va tugatish ===")
    t_ip = olch(iplar_yarat, 100)
    t_kor = olch(korutinalar_yarat, 100)
    t_jar = olch(jarayonlar_yarat, 8, takror=1)
    print(f"  korutinalar iplardan tez: {t_kor < t_ip}")
    print(f"  8 jarayon 100 ipdan ham uzoq vaqt oldi: {t_jar > t_ip}")
    print(f"  jarayon yaratish korutinadan kamida 20 barobar qimmat (bittasiga): "
          f"{(t_jar / 8) / (t_kor / 100) >= 20}")

    print("\n=== 2. Miqyos: 10 000 birlik ===")
    t_10k = olch(korutinalar_yarat, 10_000, takror=1)
    print(f"  10 000 korutina yaratildi va tugadi: {t_10k < 5.0}")
    print(f"  bir korutinaga o'rtacha vaqt 100 korutinadagidan yomonlashmadi: "
          f"{t_10k / 10_000 <= (t_kor / 100) * 3}")
    print("  ⚠️ 10 000 ip yaratish ko'p tizimlarda xotira va yadro chegarasiga uriladi")

    print("\n=== 3. Vazifaga qancha xotira ===")

    async def kop_vazifa(n: int) -> int:
        vazifalar = [asyncio.create_task(asyncio.sleep(0.05)) for _ in range(n)]
        joriy, _ = tracemalloc.get_traced_memory()
        await asyncio.gather(*vazifalar)
        return joriy

    gc.collect()
    tracemalloc.start()
    xotira = asyncio.run(kop_vazifa(5_000))
    tracemalloc.stop()
    bir_vazifa = xotira / 5_000
    print(f"  5 000 vazifa uchun ~{xotira / 1e6:.1f} MB, bittasiga ~{bir_vazifa:.0f} bayt")
    print(f"  bir vazifa 5 KB dan kam: {bir_vazifa < 5_000}")
    print(f"  taqqoslash uchun: ip steki sukut bo'yicha kamida 64 KB (OT ga bog'liq)")

    print("\n=== 4. Amaliy chegaralar ===")
    jadval = [
        ("jarayon", "yadrolar soni × 1–2", "xotira, kontekst almashinuvi"),
        ("ip", "yuzlab – minglab", "stek xotirasi, yadro resurslari"),
        ("korutina", "yuz minglab", "faqat xotira"),
    ]
    for model, chegara, sabab in jadval:
        print(f"  {model:10} {chegara:24} {sabab}")
    print(f"  bu mashinada yadrolar: {mp.cpu_count() >= 1}, faol iplar: {threading.active_count()}")

Natijaning muhim qismi:

text
=== 1. 100 birlik yaratish va tugatish ===
  korutinalar iplardan tez: True
  8 jarayon 100 ipdan ham uzoq vaqt oldi: True
  jarayon yaratish korutinadan kamida 20 barobar qimmat (bittasiga): True

=== 2. Miqyos: 10 000 birlik ===
  10 000 korutina yaratildi va tugadi: True
  bir korutinaga o'rtacha vaqt 100 korutinadagidan yomonlashmadi: True
  ⚠️ 10 000 ip yaratish ko'p tizimlarda xotira va yadro chegarasiga uriladi

=== 3. Vazifaga qancha xotira ===
  5 000 vazifa uchun ~3.7 MB, bittasiga ~746 bayt
  bir vazifa 5 KB dan kam: True
  taqqoslash uchun: ip steki sukut bo'yicha kamida 64 KB (OT ga bog'liq)

=== 4. Amaliy chegaralar ===
  jarayon    yadrolar soni × 1–2      xotira, kontekst almashinuvi
  ip         yuzlab – minglab         stek xotirasi, yadro resurslari
  korutina   yuz minglab              faqat xotira
  bu mashinada yadrolar: True, faol iplar: 1

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

Misol 3 — Rejalashtirish: majburiy va hamkorlikdagi

python
"""Iplar o'z-o'zidan almashadi; korutinalar faqat await da; bloklovchi korutina butun siklni to'xtatadi; yechim."""

import asyncio
import threading
import time


def og_ir_hisob(n: int = 300_000) -> int:
    s = 0
    for i in range(n):
        s += i * i
    return s


print("=== 1. Iplar: majburiy almashinuv ===")
jurnal: list[str] = []


def ip_ishchisi(nom: str) -> None:
    for _ in range(4):
        og_ir_hisob()
        jurnal.append(nom)


iplar = [threading.Thread(target=ip_ishchisi, args=(nom,)) for nom in "AB"]
for ip in iplar:
    ip.start()
for ip in iplar:
    ip.join()
almashinuvlar = sum(1 for a, b in zip(jurnal, jurnal[1:]) if a != b)
print(f"  har ip {len(jurnal) // 2} bosqichni bajardi, jami yozuvlar: {len(jurnal)}")
print(f"  iplar hech qanday 'await' yozmasdan almashdi: {almashinuvlar >= 1}")

print("\n=== 2. Korutinalar: faqat await da ===")


async def yaxshi_ishchi(nom: str, jurnal: list[str]) -> None:
    for _ in range(4):
        await asyncio.sleep(0)          # navbatni boshqalarga berish
        jurnal.append(nom)


async def yaxshi() -> list[str]:
    j: list[str] = []
    await asyncio.gather(yaxshi_ishchi("A", j), yaxshi_ishchi("B", j))
    return j


natija = asyncio.run(yaxshi())
print(f"  await bilan: {''.join(natija)} — navbatma-navbat")

print("\n=== 3. ⚠️ Bloklovchi korutina ===")


async def bloklovchi_ishchi(nom: str, jurnal: list[str]) -> None:
    for _ in range(4):
        og_ir_hisob()                   # await yo'q — sikl to'xtaydi
        jurnal.append(nom)


async def bloklovchi() -> list[str]:
    j: list[str] = []
    await asyncio.gather(bloklovchi_ishchi("A", j), bloklovchi_ishchi("B", j))
    return j


natija = asyncio.run(bloklovchi())
print(f"  await siz: {''.join(natija)} — A tugamaguncha B boshlanmadi")

print("\n=== 4. Hodisa siklining kechikishi ===")


async def olchov() -> tuple[float, float]:
    """Sikl 'yurak urishi': har 10 ms da belgilanishi kerak."""
    kechikishlar: list[float] = []

    async def yurak() -> None:
        oldingi = time.perf_counter()
        for _ in range(20):
            await asyncio.sleep(0.01)
            hozir = time.perf_counter()
            kechikishlar.append(hozir - oldingi - 0.01)
            oldingi = hozir

    async def bloklovchi_vazifa() -> None:
        await asyncio.sleep(0.05)
        og_ir_hisob(3_000_000)          # ⚠️ siklni bloklaydi

    await asyncio.gather(yurak(), bloklovchi_vazifa())
    return max(kechikishlar), sum(kechikishlar) / len(kechikishlar)


eng_katta, ortacha = asyncio.run(olchov())
print(f"  eng katta kechikish o'rtachadan kamida 5 barobar katta: {eng_katta > ortacha * 5}")
print(f"  ⭐ bitta bloklovchi qism butun siklga ta'sir qiladi")
print(f"  ✅ yechim: og'ir hisobni asyncio.to_thread yoki jarayonga chiqarish (14.7-dars)")

Natijaning muhim qismi:

text
=== 1. Iplar: majburiy almashinuv ===
  har ip 4 bosqichni bajardi, jami yozuvlar: 8
  iplar hech qanday 'await' yozmasdan almashdi: True

=== 2. Korutinalar: faqat await da ===
  await bilan: ABABABAB — navbatma-navbat

=== 3. ⚠️ Bloklovchi korutina ===
  await siz: AAAABBBB — A tugamaguncha B boshlanmadi

=== 4. Hodisa siklining kechikishi ===
  eng katta kechikish o'rtachadan kamida 5 barobar katta: True
  ⭐ bitta bloklovchi qism butun siklga ta'sir qiladi
  ✅ yechim: og'ir hisobni asyncio.to_thread yoki jarayonga chiqarish (14.7-dars)

Nima ko'rsatdi: 2.3-bo'lim.

Misol 4 — Amaliy: ish turiga qarab model tanlash

Xabarnoma servisi: 5 000 ta qurilmaga push yuborish (I/O), keyin hisobotni hisoblash (CPU). Vosita ikkala bosqichni o'lchab, mos modelni tanlaydi va tanlovni asoslaydi.

python
"""I/O bosqichi: ketma-ket, iplar, korutinalar; CPU bosqichi: iplar va jarayonlar; tanlov va asos."""

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

QURILMALAR = [f"qurilma-{i:05d}" for i in range(400)]
KUTISH = 0.005


def push_yubor(qurilma: str) -> str:
    time.sleep(KUTISH)                      # tarmoq
    return f"{qurilma}:ok"


async def async_push(qurilma: str) -> str:
    await asyncio.sleep(KUTISH)
    return f"{qurilma}:ok"


def hisobot_bolagi(bolak: list[str]) -> str:
    """Sof Python CPU ish (12.6-dars)."""
    h = hashlib.sha256()
    for q in bolak:
        h.update(q.encode())
        for _ in range(200):
            h.update(b"x")
    return h.hexdigest()[:8]


def ketma_ket_io() -> list[str]:
    return [push_yubor(q) for q in QURILMALAR]


def iplar_io(ishchilar: int = 64) -> list[str]:
    with cf.ThreadPoolExecutor(max_workers=ishchilar) as ex:
        return list(ex.map(push_yubor, QURILMALAR))


def korutinalar_io() -> list[str]:
    async def hammasi() -> list[str]:
        return list(await asyncio.gather(*(async_push(q) for q in QURILMALAR)))
    return asyncio.run(hammasi())


def olch(funksiya, *args) -> tuple[float, object]:
    bosh = time.perf_counter()
    natija = funksiya(*args)
    return time.perf_counter() - bosh, natija


if __name__ == "__main__":
    print("=== 1. I/O bosqichi ===")
    t_ket, n_ket = olch(ketma_ket_io)
    t_ip, n_ip = olch(iplar_io)
    t_kor, n_kor = olch(korutinalar_io)
    print(f"  {len(QURILMALAR)} ta 'so'rov', har biri {KUTISH * 1000:.0f} ms")
    print(f"  natijalar bir xil: {n_ket == n_ip == n_kor}")
    print(f"  iplar ketma-ketdan kamida 5 barobar tez: {t_ket / t_ip >= 5}")
    print(f"  korutinalar ham: {t_ket / t_kor >= 5}")
    print(f"  korutinalar 400 ta 'ulanish' uchun atigi bitta ip ishlatdi")

    print("\n=== 2. CPU bosqichi ===")
    bolaklar = [QURILMALAR[i::4] for i in range(4)]
    t_ket_cpu, _ = olch(lambda: [hisobot_bolagi(b) for b in bolaklar])
    with cf.ThreadPoolExecutor(max_workers=4) as ex:
        t_ip_cpu, _ = olch(lambda: list(ex.map(hisobot_bolagi, bolaklar)))
    with cf.ProcessPoolExecutor(max_workers=4) as pex:
        list(pex.map(hisobot_bolagi, bolaklar[:1]))          # isitish
        t_jar_cpu, _ = olch(lambda: list(pex.map(hisobot_bolagi, bolaklar)))
    print(f"  iplar ketma-ketga yaqin (GIL): {t_ip_cpu > t_ket_cpu * 0.7}")
    print(f"  jarayonlar iplardan tez: {t_jar_cpu < t_ip_cpu}")

    print("\n=== 3. Tanlov ===")
    tanlovlar = [
        ("I/O, minglab ulanish", "korutinalar (asyncio)", "eng arzon, bitta ip"),
        ("I/O, bloklovchi kutubxona", "iplar", "kod o'zgarmaydi"),
        ("sof Python CPU", "jarayonlar", "GIL ni chetlab o'tadi"),
        ("C kutubxona (NumPy, hashlib katta bufer)", "iplar", "GIL bo'shatiladi"),
        ("ishonchsiz kod", "jarayonlar", "izolyatsiya"),
    ]
    for ish, model, sabab in tanlovlar:
        print(f"  {ish:42} → {model:22} ({sabab})")

    print("\n=== 4. Ushbu servis uchun ===")
    print("  push yuborish  → asyncio (5 000 ulanish, har biri kutish bilan)")
    print("  hisobot        → ProcessPoolExecutor (sof Python hisob)")
    print("  ⭐ bitta ilovada ikki model birga ishlatiladi (14.8-dars)")

Natijaning muhim qismi:

text
=== 1. I/O bosqichi ===
  400 ta 'so'rov', har biri 5 ms
  natijalar bir xil: True
  iplar ketma-ketdan kamida 5 barobar tez: True
  korutinalar ham: True
  korutinalar 400 ta 'ulanish' uchun atigi bitta ip ishlatdi

=== 2. CPU bosqichi ===
  iplar ketma-ketga yaqin (GIL): True
  jarayonlar iplardan tez: False

=== 3. Tanlov ===
  I/O, minglab ulanish                       → korutinalar (asyncio)  (eng arzon, bitta ip)
  I/O, bloklovchi kutubxona                  → iplar                  (kod o'zgarmaydi)
  sof Python CPU                             → jarayonlar             (GIL ni chetlab o'tadi)
  C kutubxona (NumPy, hashlib katta bufer)   → iplar                  (GIL bo'shatiladi)
  ishonchsiz kod                             → jarayonlar             (izolyatsiya)

=== 4. Ushbu servis uchun ===
  push yuborish  → asyncio (5 000 ulanish, har biri kutish bilan)
  hisobot        → ProcessPoolExecutor (sof Python hisob)
  ⭐ bitta ilovada ikki model birga ishlatiladi (14.8-dars)

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


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

Noto'g'ri fikr To'g'risi
"asyncio — parallellik" Konkurentlik: bitta ip, await da almashinuv
"Iplar har doim korutinalardan sekin" I/O da farq kichik; minglab ulanishda korutinalar yutadi
"Har ulanishga bitta ip — normal" 10 000 ulanishda xotira va almashinuv narxi ortadi
"Korutinada time.sleep ishlaydi" Ishlaydi, lekin butun siklni bloklaydi
"Jarayonlar har doim tezroq" Yaratish va ma'lumot uzatish qimmat (12.6-dars)
"Iplar CPU ishni tezlashtiradi" Sof Python kodda — yo'q (GIL)
"Korutinalarda poyga holati yo'q" Kamroq, lekin await orasida holat o'zgarishi mumkin
"Model tanlash — did masalasi" O'lchov masalasi: ish turi va miqyosga bog'liq

6. Keng tarqalgan xatolar va yechimlari

1. Har ulanishga ip

python
for ulanish in ulanishlar:                 # ❌ 10 000 ip
    threading.Thread(target=xizmat, args=(ulanish,)).start()

Pul (ThreadPoolExecutor) yoki asyncio.

2. Korutinada bloklovchi chaqiruv

python
async def yukla(u: str) -> str:
    return requests.get(u).text            # ❌ butun siklni to'xtatadi
    return await mijoz.get(u)              # ✅ async kutubxona
    return await asyncio.to_thread(requests.get, u)   # ✅ iplarga chiqarish

3. CPU ishni asyncio ga berish

python
async def hisobla() -> int:
    return og_ir_hisob()                   # ❌ sikl bloklanadi

ProcessPoolExecutor yoki asyncio.to_thread (GIL bo'shatuvchi kod uchun).

4. Jarayonlarni mayda ish uchun ishlatish

python
with ProcessPoolExecutor() as ex:
    ex.map(kichik_funksiya, 100_000 * [...])   # ❌ uzatish narxi (12.6-dars)

5. Modellarni chalkashtirish

python
threading.Thread(target=async_funksiya)    # ❌ korutina chaqirilmaydi
asyncio.run(async_funksiya())              # ✅

6. Umumiy holatni qulfsiz o'zgartirish (iplar)

python
self.hisob += 1                            # ❌ 12.5-dars
with self.qulf: self.hisob += 1            # ✅

7. Jarayonlar orasida obyektlarni "ulashishga" urinish

python
umumiy = {}                                # ❌ har jarayonda o'z nusxasi
manager = multiprocessing.Manager()        # ✅ yoki navbat/shared_memory (14.4-dars)

8. asyncio.run ni sikl ichida chaqirish

python
for u in manzillar:
    asyncio.run(yukla(u))                  # ❌ har safar yangi sikl, konkurentlik yo'q
asyncio.run(hammasi(manzillar))            # ✅ bitta sikl, gather bilan

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 12.3-dars (o'tilgan): freymlar — korutinalar to'xtatilgan freym
  • 12.5–12.6-darslar (o'tilgan): GIL va strategiyalar
  • 14.2–14.3-darslar: threading, poyga holatlari, qulflar
  • 14.4–14.5-darslar: multiprocessing, pullar
  • 14.6–14.7-darslar: asyncio va async/await
  • 14.8-dars: tanlov qoidalari
  • 20-qism: veb-serverlar — har so'rov uchun model
  • 29-qism: miqyoslash va yuk testi

8. Eng yaxshi amaliyotlar

  1. Ish turini aniqlang (I/O yoki CPU) — keyin model tanlang.

  2. Minglab ulanish — korutinalar.

  3. Bloklovchi kutubxonalar — iplar (yoki asyncio.to_thread).

  4. Sof Python CPU — jarayonlar.

  5. Korutinada hech qachon bloklovchi chaqiruv qoldirmang.

  6. Loglarda PID, ip va vazifa nomini chiqaring.

  7. Pul (pool) ishlating — cheksiz ip/jarayon yaratmang.

  8. Tanlovni o'lchov bilan asoslang, taxmin bilan emas.


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import asyncio
import os
import threading
1.  print(threading.current_thread().name)
2.  print(threading.active_count())
3.  ip = threading.Thread(target=lambda: None)
    print(ip.is_alive())
4.  ip.start(); ip.join()
    print(ip.is_alive())
5.  print(type(os.getpid()).__name__)
6.  async def f():
        return 42
    print(asyncio.run(f()))
7.  async def g():
        return asyncio.current_task().get_name()
    print(asyncio.run(g()).startswith("Task"))
8.  async def h():
        await asyncio.sleep(0)
        return "tayyor"
    print(asyncio.run(h()))
9.  print(asyncio.iscoroutinefunction(h), asyncio.iscoroutinefunction(print))
10. c = h()
    print(type(c).__name__)
    c.close()
11. import multiprocessing as mp
    print(mp.cpu_count() >= 1)
12. print(threading.get_ident() == threading.main_thread().ident)
Javoblar
  1. MainThread
  2. 1
  3. False — hali ishga tushmagan
  4. False — tugagan
  5. int
  6. 42
  7. True — sukut nomlar Task-1 ko'rinishida
  8. tayyor
  9. True False
  10. coroutine
  11. True
  12. True

Vazifa 2: Xatolarni tuzating

python
1.  for ulanish in ulanishlar:      # 5000 ta
        threading.Thread(target=xizmat, args=(ulanish,)).start()

2.  async def yukla(manzil):
        return requests.get(manzil).text

3.  async def hisobot(malumot):
        return og_ir_hisob(malumot)

4.  for manzil in manzillar:
        natija = asyncio.run(yukla(manzil))

5.  ip = threading.Thread(target=async_funksiya())
    ip.start()
Javoblar
python
1.  with ThreadPoolExecutor(max_workers=64) as ex:     # yoki asyncio
        list(ex.map(xizmat, ulanishlar))

2.  async def yukla(manzil: str) -> str:
        async with httpx.AsyncClient() as mijoz:       # async kutubxona
            javob = await mijoz.get(manzil)
            return javob.text
    # yoki: return await asyncio.to_thread(lambda: requests.get(manzil).text)

3.  async def hisobot(malumot: bytes) -> str:
        sikl = asyncio.get_running_loop()
        with ProcessPoolExecutor() as pul:             # CPU ishni jarayonga
            return await sikl.run_in_executor(pul, og_ir_hisob, malumot)

4.  async def hammasi(manzillar: list[str]) -> list[str]:
        return list(await asyncio.gather(*(yukla(m) for m in manzillar)))
    natijalar = asyncio.run(hammasi(manzillar))        # bitta sikl

5.  asyncio.run(async_funksiya())                      # korutina — sikl bilan
    # yoki ipda: threading.Thread(target=asyncio.run, args=(async_funksiya(),))

Vazifa 3: O'lchov jadvali

  1. 10, 100, 1 000, 10 000 birlik uchun iplar va korutinalar yaratish vaqtini o'lchang
  2. Har biri uchun xotira sarfini tracemalloc bilan qayd eting
  3. Qaysi nuqtada iplar amaliy bo'lmay qolishini aniqlang
  4. Natijalarni jadval va qisqa xulosa qilib rasmiylashtiring

Vazifa 4: Hodisa sikli kechikishi

  1. asyncio da "yurak urishi" vazifasini yozing (har 10 ms)
  2. Uning yonida turli davomiylikdagi bloklovchi funksiyalarni ishga tushiring
  3. Kechikish taqsimotini (min, o'rtacha, p95, maks) chiqaring
  4. Bloklovchi qismni asyncio.to_thread ga chiqarib, farqni ko'rsating
  5. Qaysi davomiylikdan boshlab bloklash sezilarli bo'lishini aniqlang

Vazifa 5: Identifikatorli log

  1. Har log yozuvida PID, ip nomi va vazifa nomini chiqaradigan formatter yozing
  2. Uni uch modelda ham (iplar, jarayonlar, korutinalar) sinang
  3. Yozuvlarni fayldan o'qib, model bo'yicha statistika chiqaring
  4. contextvars bilan so'rov identifikatorini uzatishni qo'shing (14.7-darsga tayyorgarlik)

Vazifa 6: Aralash quvur

Rasm servisini yozing:

  1. Rasmlarni "yuklab olish" (I/O, asyncio)
  2. Har rasm uchun "eskiz" yasash (CPU, jarayonlar)
  3. Natijani "saqlash" (I/O)
  4. Uch bosqichni bitta quvurda birlashtiring va umumiy vaqtni o'lchang
  5. Har bosqich uchun model tanlovini asoslang

Vazifa 7: O'ylash

Go'da goroutine — til darajasidagi yengil oqim (M:N rejalashtiruvchi bilan), Erlang'da jarayonlar juda arzon va izolyatsiyalangan, Java 21 da virtual iplar paydo bo'ldi, Node.js esa bitta ipli hodisa sikliga tayanadi. Python'dagi uch model shu yondashuvlar bilan qanday taqqoslanadi va nega Python'da "yengil oqim" til darajasida yo'q?

Javob

Qisqa javob: Go, Erlang va Java 21 ish vaqti (runtime) darajasida yengil oqimlarni qo'llab-quvvatlaydi: dasturchi oddiy bloklovchi kod yozadi, rejalashtiruvchi esa uni avtomatik almashtiradi. Python'da esa bu kutubxona darajasida (asyncio) amalga oshirilgan, shuning uchun async/await sintaksisi va "rangli funksiyalar" muammosi paydo bo'ladi.

1. Taqqoslash

Platforma Yengil birlik Rejalashtirish Kod uslubi
Go Goroutine M:N, runtime Oddiy bloklovchi kod
Erlang Jarayon (~300 bayt) Preemptive, runtime Xabar almashish
Java 21+ Virtual ip M:N, JVM Oddiy bloklovchi kod
Node.js Callback/Promise Bitta ipli sikl async/await
Python Korutina Hamkorlikdagi, kutubxona async/await

2. "Rangli funksiyalar" muammosi

python
def oddiy(): ...
async def korutina(): ...

await oddiy()          # ❌ mumkin emas
korutina()             # ⚠️ hech narsa bajarilmaydi — await kerak
  • Async funksiyani faqat async kontekstda chaqirish mumkin — kod "ikki rangga" bo'linadi
  • Kutubxonalar ham ikki nusxada bo'ladi (requests va httpx, psycopg va asyncpg)
  • Go va Java'da bu muammo yo'q: bir xil kod ikkala rejimda ishlaydi

3. Nega Python'da til darajasida yo'q

  1. Moslik: C API va mavjud kod bloklovchi stekka tayanadi; virtual iplarni qo'shish uni buzardi
  2. CPython arxitekturasi: har korutina uchun C stekini saqlash kerak bo'lardi (12.3-dars)
  3. Tarix: asyncio 2014-yilda kutubxona sifatida qo'shildi va PEP 492 bilan sintaksis oldi
  4. Muqobil yo'l: 3.14 dagi erkin ipli yig'ma va arzonlashgan iplar — bloklovchi kodni saqlab qolgan holda miqyosni oshirish urinishi

4. Amaliy xulosa

  1. Python'da model tanlash — dasturchi zimmasida (bu darsning mavzusi)
  2. asyncio ekotizimini tanlasangiz, butun zanjir async bo'lishi kerak
  3. Bloklovchi kutubxonalarni asyncio.to_thread bilan ulash mumkin — "ko'prik" naqshi
  4. Kelajakda erkin ipli yig'ma bloklovchi uslubni yana jozibador qilishi mumkin

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda konkurentlikning uch modelini taqqosladik.

Eng muhim uch fikr:

  1. Uch model — uch xil narx. Jarayon o'z manzil fazosiga ega (izolyatsiya va haqiqiy parallellik, lekin millisekundlarda yaratiladi va ma'lumot uzatish qimmat); ip umumiy xotirada ishlaydi (arzonroq, lekin qulflar kerak va GIL tufayli Python kodi parallel emas); korutina esa eng arzoni (~1 KB, mikrosoniyalarda) — minglab ulanish uchun yagona amaliy yo'l.

  2. Konkurentlik — parallellik emas. asyncio bitta ipda ishlaydi va faqat kutish vaqtini foydali ishga aylantiradi. Iplar majburiy almashadi (kod hech narsa yozmasa ham), korutinalar esa faqat await da — shuning uchun bitta bloklovchi chaqiruv butun siklni to'xtatadi.

  3. Model ish turiga qarab tanlanadi. Minglab tarmoq so'rovi — korutinalar; bloklovchi kutubxona bilan I/O — iplar; sof Python CPU hisob — jarayonlar; ishonchsiz yoki yiqiluvchan kod — yana jarayonlar (izolyatsiya uchun). Bitta ilovada bir nechta model birga ishlatilishi normal holat.

Keyingi darsda iplar bilan amaliy ishlashni boshlaymiz: threading moduli, ip yaratish va kutish, daemon iplar, ThreadPoolExecutor va iplar bilan ishlashning xavfsiz naqshlari.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
14.1-dars: Jarayon, oqim, korutina farqi — IlmHamroh