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

14.4-dars: multiprocessing

14-QISM — KONKURENTLIK VA PARALLELLIK · 4-dars


1. Kirish va motivatsiya

14.2–14.3-darslarda iplar bilan ishladik. Ular I/O uchun zo'r, lekin sof Python hisob-kitobda GIL to'sqinlik qiladi (12.6-dars). Endi haqiqiy parallellikka o'tamiz: jarayonlar.

Real vaziyat. Geologik ma'lumotlarni qayta ishlash skripti 40 daqiqa ishlardi: 16 yadroli serverda bitta yadro 100%, qolgan 15 tasi bo'sh. Jamoa multiprocessing ga o'tdi va uch muammoga duch keldi:

  1. Skript cheksiz ko'payib ketdi: Windows'da if __name__ == "__main__" yozilmagani uchun har bola jarayon modulni qayta import qilib, yana jarayonlar yaratdi
  2. Ishchiga lambda uzatilgan edi — PicklingError
  3. Natijalar Queue orqali qaytarilardi, lekin jarayon queue.get() dan oldin join() qilingani uchun dastur abadiy osilib qoldi

Tuzatilgandan keyin ish 3 daqiqada tugadi — 13 barobar tez.

Jarayonlar kuchli, lekin ular alohida Python interpretatorlari: ular xotirani bo'lishmaydi, hamma narsa pickle orqali o'tadi va ishga tushirish qimmat. Shu uch haqiqatni bilsangiz, multiprocessing bilan ishlash oson.

Bu darsda:

  • Process: yaratish, join, exitcode, terminate
  • Ishga tushirish usullari: spawn, fork, forkserver va __main__ qoidasi
  • pickle talablari: nima uzatish mumkin, nima mumkin emas
  • Ma'lumot almashish: Queue, Pipe, Value/Array, shared_memory, Manager
  • Xatolar va to'xtatish
  • Jarayonlardan log yozish
  • Amaliy: parallel qayta ishlash quvuri

2. Nazariya — chuqur tushuntirish

2.1. Process

python
jarayon = mp.Process(target=funksiya, args=(1,), name="ishchi", daemon=False)
jarayon.start()
jarayon.join(timeout=30)
jarayon.exitcode        # 0 — muvaffaqiyat, >0 — xato, <0 — signal
Atribut Ma'nosi
pid Jarayon identifikatori
exitcode None (hali ishlayapti), 0, 1 (istisno), -15 (terminate)
is_alive() Ishlayaptimi
terminate() SIGTERM — tozalashsiz to'xtatish
kill() SIGKILL — darhol
daemon=True Ota jarayon tugashida bolalar ham tugatiladi

Iplardan farqli o'laroq, jarayonni majburan to'xtatish mumkin (terminate) — lekin bu ham tozalashni bajarmaydi.

2.2. Ishga tushirish usullari

Usul Qayerda sukut Qanday ishlaydi
spawn Windows, macOS Yangi Python ishga tushadi, modul qayta import qilinadi
fork Linux (3.13 gacha) Jarayon nusxalanadi — tez, lekin iplar bilan xavfli
forkserver Linux (3.14 dan) Toza yordamchi jarayondan fork — xavfsizroq
python
mp.set_start_method("spawn", force=True)    # dastur boshida bir marta

__main__ qoidasi: spawn va forkserver da bola jarayon modulni qayta import qiladi. Shuning uchun ishga tushirish kodi albatta himoyalangan bo'lishi kerak:

python
def main() -> None:
    ...

if __name__ == "__main__":      # ✅ shartsiz — cheksiz jarayon ko'payishi
    main()

2.3. pickle talablari

Jarayonga uzatiladigan hamma narsa seriyalanishi kerak:

Uzatiladi Uzatilmaydi
Modul darajasidagi funksiyalar lambda, ichki (nested) funksiyalar
dataclass, oddiy obyektlar Fayl obyektlari, soketlar, ulanishlar
int, str, list, dict, Decimal Qulflar (faqat mp.Lock ni argument sifatida)
functools.partial (agar ichidagilar seriyalansa) Generatorlar, korutinalar

fork da bu talab yumshoqroq (xotira nusxalanadi), lekin ko'chuvchan kod spawn ga mo'ljallangan bo'lishi kerak.

2.4. Ma'lumot almashish

Vosita Nima uchun Xususiyati
mp.Queue Vazifalar va natijalar pickle, ip uchun ham xavfsiz
mp.Pipe Ikki jarayon orasida Tezroq, lekin faqat ikki uch
mp.Value, mp.Array Oddiy sonlar/massivlar Umumiy xotira, Lock bilan
shared_memory.SharedMemory Katta massivlar (NumPy) Nusxasiz — eng tez
mp.Manager() dict, list, Namespace Qulay, lekin sekin (har amal — IPC)
python
v = mp.Value("i", 0); qulf = mp.Lock()
with qulf:
    v.value += 1                      # umumiy xotira, qulf bilan

shm = shared_memory.SharedMemory(create=True, size=1_000_000)
shm.buf[0] = 42                       # nusxasiz
shm.close(); shm.unlink()             # egasi unlink qiladi

Qoida: kichik ma'lumot — Queue; katta massiv — shared_memory; qulaylik kerak bo'lsa — Manager (narxini bilib).

2.5. Xatolar va to'xtatish

Holat Nima bo'ladi
Bolada istisno Traceback stderr ga chiqadi, exitcode = 1
terminate() exitcode = -15, finally bajarilmaydi
Ota yiqildi daemon=True bolalar tugatiladi; oddiy bolalar qoladi ("yetim")
join() bo'sh bo'lmagan Queue bilan Deadlock: avval get(), keyin join()

Xatoni otaga yetkazish uchun: natijani Queue orqali (("xato", matn)) yoki ProcessPoolExecutor (14.5-dars) — u istisnoni Future ga solib beradi.

2.6. Jarayonlardan log yozish

  • Har jarayon o'z stdout/stderr iga yozadi — aralashib ketishi mumkin
  • To'g'ri yechim: QueueHandler + otada QueueListener (15-qism)
  • Oddiy holat: har jarayon o'z faylini yozadi yoki natijani otaga qaytaradi

2.7. Qachon jarayon kerak emas

Holat Yaxshiroq yechim
I/O kutish Iplar yoki asyncio
Mayda vazifalar Bo'laklash yoki ketma-ket (12.6-dars)
Katta ma'lumot uzatish kerak shared_memory yoki fayl
Bitta yadroli mashina Parallellikdan foyda yo'q

3. Tez ma'lumotnoma

python
import multiprocessing as mp

def ishchi(x, natijalar): natijalar.put(x * x)      # modul darajasida!

if __name__ == "__main__":                          # majburiy
    mp.set_start_method("spawn", force=True)
    natijalar = mp.Queue()
    p = mp.Process(target=ishchi, args=(5, natijalar))
    p.start()
    qiymat = natijalar.get(timeout=10)              # avval get
    p.join(timeout=10)                              # keyin join
    print(qiymat, p.exitcode)

Almashish

Queue/Pipe → pickle    Value/Array → umumiy xotira + Lock
shared_memory → katta massivlar, nusxasiz
Manager → qulay, sekin

4. Batafsil misollar

Misol 1 — Process asoslari

python
"""Jarayon yaratish; alohida PID va xotira; exitcode; terminate; daemon; __main__ qoidasi."""

import multiprocessing as mp
import os
import sys
import time

HISOB = 0          # har jarayonda o'z nusxasi bo'ladi


def kim_men(navbat: mp.Queue) -> None:
    global HISOB
    HISOB += 100
    navbat.put((os.getpid(), HISOB))


def uzoq_ishchi() -> None:
    time.sleep(30)


def chiqish_kodi_bilan(kod: int) -> None:
    sys.exit(kod)


def main() -> None:
    print("=== 1. Alohida jarayonlar ===")
    navbat: mp.Queue = mp.Queue()
    jarayonlar = [mp.Process(target=kim_men, args=(navbat,), name=f"ishchi-{i}") for i in range(3)]
    for j in jarayonlar:
        j.start()
    natijalar = [navbat.get(timeout=15) for _ in jarayonlar]
    for j in jarayonlar:
        j.join(timeout=15)
    pidlar = {pid for pid, _ in natijalar}
    print(f"  {len(pidlar)} xil PID, asosiy jarayon ular orasida emas: {os.getpid() not in pidlar}")
    print(f"  har jarayonda HISOB mustaqil o'zgardi: {sorted(h for _, h in natijalar)}")
    print(f"  asosiy jarayonda HISOB o'zgarmadi: {HISOB}")
    print(f"  exitcode lar: {[j.exitcode for j in jarayonlar]}")

    print("\n=== 2. exitcode ===")
    normal = mp.Process(target=chiqish_kodi_bilan, args=(0,))
    normal.start()
    normal.join(timeout=15)
    xatoli = mp.Process(target=chiqish_kodi_bilan, args=(3,))
    xatoli.start()
    xatoli.join(timeout=15)
    print(f"  sys.exit(0) → exitcode {normal.exitcode}")
    print(f"  sys.exit(3) → exitcode {xatoli.exitcode}")
    print("  (ushlanmagan istisno → exitcode 1, traceback bolaning stderr iga chiqadi)")

    print("\n=== 3. terminate ===")
    uzoq = mp.Process(target=uzoq_ishchi, daemon=True, name="uzoq")
    uzoq.start()
    time.sleep(0.2)
    print(f"  ishlayaptimi: {uzoq.is_alive()}")
    uzoq.terminate()
    uzoq.join(timeout=10)
    print(f"  terminate dan keyin: tirik={uzoq.is_alive()}, exitcode={uzoq.exitcode} (manfiy — signal)")
    print("  ⚠️ terminate tozalashni (finally, with) bajarmaydi")

    print("\n=== 4. Ishga tushirish usuli ===")
    print(f"  joriy usul: {mp.get_start_method()}")
    print(f"  mavjud usullar: {sorted(mp.get_all_start_methods())}")
    print(f"  yadrolar: {mp.cpu_count() >= 1}")
    print("  ⭐ spawn/forkserver da bola modulni qayta import qiladi —")
    print("     shuning uchun butun ishga tushirish kodi if __name__ == '__main__' ichida")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Alohida jarayonlar ===
  3 xil PID, asosiy jarayon ular orasida emas: True
  har jarayonda HISOB mustaqil o'zgardi: [100, 100, 100]
  asosiy jarayonda HISOB o'zgarmadi: 0
  exitcode lar: [0, 0, 0]

=== 2. exitcode ===
  sys.exit(0) → exitcode 0
  sys.exit(3) → exitcode 3
  (ushlanmagan istisno → exitcode 1, traceback bolaning stderr iga chiqadi)

=== 3. terminate ===
  ishlayaptimi: True
  terminate dan keyin: tirik=False, exitcode=-15 (manfiy — signal)
  ⚠️ terminate tozalashni (finally, with) bajarmaydi

=== 4. Ishga tushirish usuli ===
  joriy usul: spawn
  mavjud usullar: ['spawn']
  yadrolar: True
  ⭐ spawn/forkserver da bola modulni qayta import qiladi —
     shuning uchun butun ishga tushirish kodi if __name__ == '__main__' ichida

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

Misol 2 — pickle talablari

python
"""Nima uzatiladi va nima uzatilmaydi; lambda va ichki funksiya xatosi; partial; dataclass; fayl obyekti."""

import functools
import io
import multiprocessing as mp
import pickle
from dataclasses import dataclass


@dataclass(frozen=True, slots=True)
class Vazifa:
    id: int
    nom: str


def modul_funksiyasi(x: int) -> int:
    return x * 2


def koeffitsient_bilan(x: int, koeffitsient: int = 3) -> int:
    return x * koeffitsient


def bajar(funksiya, qiymat, navbat: mp.Queue) -> None:
    navbat.put(funksiya(qiymat))


def main() -> None:
    print("=== 1. Nima pickle qilinadi ===")
    ichki = lambda x: x * 2          # noqa: E731 — namuna uchun

    def ichki_funksiya(x: int) -> int:
        return x * 2

    namunalar = [
        ("modul funksiyasi", modul_funksiyasi),
        ("lambda", ichki),
        ("ichki funksiya", ichki_funksiya),
        ("partial", functools.partial(koeffitsient_bilan, koeffitsient=5)),
        ("dataclass", Vazifa(1, "hisobot")),
        ("ro'yxat", [1, 2, 3]),
        ("fayl obyekti", io.StringIO("matn")),
        ("qulf (threading)", __import__("threading").Lock()),
    ]
    for tavsif, obyekt in namunalar:
        try:
            pickle.dumps(obyekt)
            holat = "✅ uzatiladi"
        except Exception as xato:
            holat = f"❌ {type(xato).__name__}"
        print(f"  {tavsif:22} {holat}")

    print("\n=== 2. Jarayonga uzatish ===")
    navbat: mp.Queue = mp.Queue()
    jarayon = mp.Process(target=bajar, args=(modul_funksiyasi, 21, navbat))
    jarayon.start()
    print(f"  modul funksiyasi bilan: {navbat.get(timeout=15)}")
    jarayon.join(timeout=15)

    partial_funksiya = functools.partial(koeffitsient_bilan, koeffitsient=5)
    jarayon = mp.Process(target=bajar, args=(partial_funksiya, 4, navbat))
    jarayon.start()
    print(f"  partial bilan: {navbat.get(timeout=15)}")
    jarayon.join(timeout=15)

    print("\n=== 3. lambda bilan nima bo'ladi ===")
    try:
        pickle.dumps(ichki)
    except Exception as xato:
        print(f"  pickle.dumps(lambda) → {type(xato).__name__}: {str(xato)[:60]}")
    print("  ⚠️ spawn rejimida bunday target bilan Process.start() xato beradi")
    print("  ✅ yechim: modul darajasidagi funksiya yoki functools.partial")

    print("\n=== 4. Dataclass uzatish ===")
    vazifalar = [Vazifa(i, f"vazifa-{i}") for i in range(3)]
    kirish: mp.Queue = mp.Queue()
    for v in vazifalar:
        kirish.put(v)
    olingan = [kirish.get(timeout=5) for _ in vazifalar]
    print(f"  {len(olingan)} vazifa navbat orqali o'tdi: {olingan[0]}")
    print(f"  nusxalar teng, lekin boshqa obyektlar: {olingan[0] == vazifalar[0]}, {olingan[0] is vazifalar[0]}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Nima pickle qilinadi ===
  modul funksiyasi       ✅ uzatiladi
  lambda                 ❌ PicklingError
  ichki funksiya         ❌ PicklingError
  partial                ✅ uzatiladi
  dataclass              ✅ uzatiladi
  ro'yxat                ✅ uzatiladi
  fayl obyekti           ✅ uzatiladi
  qulf (threading)       ❌ TypeError

=== 2. Jarayonga uzatish ===
  modul funksiyasi bilan: 42
  partial bilan: 20

=== 3. lambda bilan nima bo'ladi ===
  pickle.dumps(lambda) → PicklingError: Can't pickle local object <function main.<locals>.<lambda> a
  ⚠️ spawn rejimida bunday target bilan Process.start() xato beradi
  ✅ yechim: modul darajasidagi funksiya yoki functools.partial

=== 4. Dataclass uzatish ===
  3 vazifa navbat orqali o'tdi: Vazifa(id=0, nom='vazifa-0')
  nusxalar teng, lekin boshqa obyektlar: True, False

Nima ko'rsatdi: 2.3-bo'lim.

Misol 3 — Ma'lumot almashish

python
"""Queue; Pipe; Value va Lock; shared_memory (nusxasiz); Manager; qaysi biri qachon."""

import multiprocessing as mp
import time
from multiprocessing import shared_memory


def navbat_ishchisi(kirish: mp.Queue, chiqish: mp.Queue) -> None:
    while True:
        vazifa = kirish.get()
        if vazifa is None:
            return
        chiqish.put(vazifa * vazifa)


def pipe_ishchisi(ulanish) -> None:
    xabar = ulanish.recv()
    ulanish.send(f"javob: {xabar.upper()}")
    ulanish.close()


def qoshuvchi(hisoblagich, qulf, marta: int) -> None:
    for _ in range(marta):
        with qulf:
            hisoblagich.value += 1


def qulfsiz_qoshuvchi(hisoblagich, marta: int) -> None:
    for _ in range(marta):
        hisoblagich.value += 1


def shm_ishchisi(nom: str, boshlanish: int, oxir: int) -> None:
    shm = shared_memory.SharedMemory(name=nom)
    for i in range(boshlanish, oxir):
        shm.buf[i] = (i * 7) % 256
    shm.close()


def manager_ishchisi(umumiy_lugat, kalit: int) -> None:
    umumiy_lugat[kalit] = kalit * kalit


def main() -> None:
    print("=== 1. Queue ===")
    kirish: mp.Queue = mp.Queue()
    chiqish: mp.Queue = mp.Queue()
    ishchilar = [mp.Process(target=navbat_ishchisi, args=(kirish, chiqish)) for _ in range(3)]
    for i in ishchilar:
        i.start()
    for n in range(12):
        kirish.put(n)
    natijalar = [chiqish.get(timeout=20) for _ in range(12)]
    for _ in ishchilar:
        kirish.put(None)
    for i in ishchilar:
        i.join(timeout=15)
    print(f"  12 vazifa, natijalar yig'indisi: {sum(natijalar)} (kutilgan {sum(n * n for n in range(12))})")
    print(f"  ishchilar to'xtadi: {all(not i.is_alive() for i in ishchilar)}")

    print("\n=== 2. Pipe ===")
    ota_uchi, bola_uchi = mp.Pipe()
    jarayon = mp.Process(target=pipe_ishchisi, args=(bola_uchi,))
    jarayon.start()
    ota_uchi.send("salom")
    print(f"  javob: {ota_uchi.recv()}")
    jarayon.join(timeout=15)

    print("\n=== 3. Value va Lock ===")
    hisoblagich = mp.Value("i", 0)
    qulf = mp.Lock()
    MARTA, ISHCHILAR = 20_000, 4
    jarayonlar = [mp.Process(target=qoshuvchi, args=(hisoblagich, qulf, MARTA)) for _ in range(ISHCHILAR)]
    for j in jarayonlar:
        j.start()
    for j in jarayonlar:
        j.join(timeout=60)
    print(f"  qulf bilan: {hisoblagich.value:,} — kutilgan {ISHCHILAR * MARTA:,}: {hisoblagich.value == ISHCHILAR * MARTA}")

    qulfsiz = mp.Value("i", 0)
    jarayonlar = [mp.Process(target=qulfsiz_qoshuvchi, args=(qulfsiz, MARTA)) for _ in range(ISHCHILAR)]
    for j in jarayonlar:
        j.start()
    for j in jarayonlar:
        j.join(timeout=60)
    print(f"  qulfsiz: yo'qotish bo'ldi: {qulfsiz.value < ISHCHILAR * MARTA}")
    print("  ⭐ jarayonlarda ham poyga holati bor — umumiy xotira umumiy holat demakdir")

    print("\n=== 4. shared_memory: nusxasiz ===")
    HAJM = 400
    shm = shared_memory.SharedMemory(create=True, size=HAJM)
    try:
        bolaklar = [(0, 100), (100, 200), (200, 300), (300, 400)]
        jarayonlar = [mp.Process(target=shm_ishchisi, args=(shm.name, a, b)) for a, b in bolaklar]
        for j in jarayonlar:
            j.start()
        for j in jarayonlar:
            j.join(timeout=20)
        kutilgan = bytes((i * 7) % 256 for i in range(HAJM))
        print(f"  {HAJM} bayt to'ldirildi, to'g'ri: {bytes(shm.buf[:HAJM]) == kutilgan}")
        print(f"  ma'lumot nusxalanmadi — jarayonlar bir xil xotiraga yozdi")
    finally:
        shm.close()
        shm.unlink()

    print("\n=== 5. Manager: qulay, lekin sekin ===")
    with mp.Manager() as menejer:
        umumiy = menejer.dict()
        jarayonlar = [mp.Process(target=manager_ishchisi, args=(umumiy, i)) for i in range(4)]
        for j in jarayonlar:
            j.start()
        for j in jarayonlar:
            j.join(timeout=20)
        print(f"  umumiy lug'at: {dict(sorted(umumiy.items()))}")
        bosh = time.perf_counter()
        for i in range(2_000):
            umumiy[i % 10] = i
        manager_vaqti = time.perf_counter() - bosh
    oddiy = {}
    bosh = time.perf_counter()
    for i in range(2_000):
        oddiy[i % 10] = i
    oddiy_vaqti = time.perf_counter() - bosh
    print(f"  2 000 yozuv: Manager oddiy dict dan kamida 10 barobar sekin: {manager_vaqti / oddiy_vaqti >= 10}")
    print("  ⭐ har amal jarayonlararo aloqa — issiq siklda ishlatmang")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Queue ===
  12 vazifa, natijalar yig'indisi: 506 (kutilgan 506)
  ishchilar to'xtadi: True

=== 2. Pipe ===
  javob: javob: SALOM

=== 3. Value va Lock ===
  qulf bilan: 80,000 — kutilgan 80,000: True
  qulfsiz: yo'qotish bo'ldi: True
  ⭐ jarayonlarda ham poyga holati bor — umumiy xotira umumiy holat demakdir

=== 4. shared_memory: nusxasiz ===
  400 bayt to'ldirildi, to'g'ri: True
  ma'lumot nusxalanmadi — jarayonlar bir xil xotiraga yozdi

=== 5. Manager: qulay, lekin sekin ===
  umumiy lug'at: {0: 0, 1: 1, 2: 4, 3: 9}
  2 000 yozuv: Manager oddiy dict dan kamida 10 barobar sekin: True
  ⭐ har amal jarayonlararo aloqa — issiq siklda ishlatmang

Nima ko'rsatdi: 2.4-bo'lim.

Misol 4 — Amaliy: parallel qayta ishlash quvuri

Kirishdagi geologik skriptning kichik modeli: katta massivni bo'laklab, jarayonlarga taqsimlaymiz, natijalarni yig'amiz. Ikki variant: Queue orqali (ma'lumot nusxalanadi) va shared_memory orqali (nusxasiz). Xatolar otaga yetkaziladi, to'xtatish qo'llab-quvvatlanadi.

python
"""Bo'laklash; Queue va shared_memory variantlari; xatolarni otaga yetkazish; vaqt va to'g'rilik."""

import multiprocessing as mp
import time
from multiprocessing import shared_memory

ELEMENTLAR = 400_000
BOLAKLAR = 4


def ogir_hisob(qiymatlar: list[int]) -> int:
    """Sof Python hisob — GIL tufayli iplar yordam bermaydi (12.6-dars)."""
    jami = 0
    for q in qiymatlar:
        jami += (q * q) % 97
    return jami


def queue_ishchisi(bolak: list[int], nomer: int, natijalar: mp.Queue) -> None:
    try:
        natijalar.put(("ok", nomer, ogir_hisob(bolak)))
    except Exception as xato:                      # noqa: BLE001 — xatoni otaga yetkazamiz
        natijalar.put(("xato", nomer, f"{type(xato).__name__}: {xato}"))


def shm_ishchisi(nom: str, boshlanish: int, oxir: int, nomer: int, natijalar: mp.Queue) -> None:
    shm = shared_memory.SharedMemory(name=nom)
    try:
        jami = 0
        for i in range(boshlanish, oxir):
            q = shm.buf[i]
            jami += (q * q) % 97
        natijalar.put(("ok", nomer, jami))
    except Exception as xato:                      # noqa: BLE001
        natijalar.put(("xato", nomer, f"{type(xato).__name__}: {xato}"))
    finally:
        shm.close()


def yiqiluvchi_ishchi(bolak: list[int], nomer: int, natijalar: mp.Queue) -> None:
    try:
        raise ValueError("ma'lumot buzilgan")
    except Exception as xato:                      # noqa: BLE001
        natijalar.put(("xato", nomer, f"{type(xato).__name__}: {xato}"))


def yig(natijalar: mp.Queue, soni: int, muddat: float = 60.0) -> tuple[int, list[str]]:
    jami, xatolar = 0, []
    for _ in range(soni):
        holat, nomer, qiymat = natijalar.get(timeout=muddat)
        if holat == "ok":
            jami += qiymat
        else:
            xatolar.append(f"bo'lak {nomer}: {qiymat}")
    return jami, xatolar


def main() -> None:
    malumot = [(i * 13) % 256 for i in range(ELEMENTLAR)]
    kutilgan = ogir_hisob(malumot)

    print("=== 1. Ketma-ket asos ===")
    bosh = time.perf_counter()
    ogir_hisob(malumot)
    ketma_ket = time.perf_counter() - bosh
    print(f"  {ELEMENTLAR:,} element, natija: {kutilgan}")

    print("\n=== 2. Queue orqali (ma'lumot nusxalanadi) ===")
    bolaklar = [malumot[i::BOLAKLAR] for i in range(BOLAKLAR)]
    natijalar: mp.Queue = mp.Queue()
    bosh = time.perf_counter()
    jarayonlar = [mp.Process(target=queue_ishchisi, args=(b, i, natijalar)) for i, b in enumerate(bolaklar)]
    for j in jarayonlar:
        j.start()
    jami, xatolar = yig(natijalar, BOLAKLAR)
    for j in jarayonlar:
        j.join(timeout=60)
    queue_vaqti = time.perf_counter() - bosh
    print(f"  natija to'g'ri: {jami == kutilgan}, xatolar: {len(xatolar)}")
    print(f"  exitcode lar: {[j.exitcode for j in jarayonlar]}")

    print("\n=== 3. shared_memory orqali (nusxasiz) ===")
    shm = shared_memory.SharedMemory(create=True, size=ELEMENTLAR)
    try:
        shm.buf[:ELEMENTLAR] = bytes(malumot)
        chegara = ELEMENTLAR // BOLAKLAR
        natijalar2: mp.Queue = mp.Queue()
        bosh = time.perf_counter()
        jarayonlar = [
            mp.Process(target=shm_ishchisi, args=(shm.name, i * chegara, (i + 1) * chegara, i, natijalar2))
            for i in range(BOLAKLAR)
        ]
        for j in jarayonlar:
            j.start()
        jami2, xatolar2 = yig(natijalar2, BOLAKLAR)
        for j in jarayonlar:
            j.join(timeout=60)
        shm_vaqti = time.perf_counter() - bosh
        print(f"  natija to'g'ri: {jami2 == kutilgan}, xatolar: {len(xatolar2)}")
    finally:
        shm.close()
        shm.unlink()

    print("\n=== 4. Vaqtlar ===")
    print(f"  jarayonlar ketma-ketdan tez (Queue): {queue_vaqti < ketma_ket}")
    print(f"  jarayonlar ketma-ketdan tez (shared_memory): {shm_vaqti < ketma_ket}")
    print(f"  shared_memory Queue dan tez yoki teng: {shm_vaqti <= queue_vaqti * 1.2}")
    print("  ⭐ ma'lumot qancha katta bo'lsa, nusxalash narxi shuncha sezilarli")

    print("\n=== 5. Xatolar otaga yetkaziladi ===")
    natijalar3: mp.Queue = mp.Queue()
    jarayonlar = [mp.Process(target=yiqiluvchi_ishchi, args=(bolaklar[0], i, natijalar3)) for i in range(2)]
    for j in jarayonlar:
        j.start()
    jami3, xatolar3 = yig(natijalar3, 2)
    for j in jarayonlar:
        j.join(timeout=30)
    print(f"  qaytgan xatolar: {len(xatolar3)}")
    print(f"  birinchisi: {xatolar3[0]}")
    print(f"  jarayonlar normal tugadi (exitcode 0): {all(j.exitcode == 0 for j in jarayonlar)}")
    print("  ⭐ xatoni ushlab, natija sifatida qaytarish — ota uni ko'radi va qaror qabul qiladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ketma-ket asos ===
  400,000 element, natija: 19292352

=== 2. Queue orqali (ma'lumot nusxalanadi) ===
  natija to'g'ri: True, xatolar: 0
  exitcode lar: [0, 0, 0, 0]

=== 3. shared_memory orqali (nusxasiz) ===
  natija to'g'ri: True, xatolar: 0

=== 4. Vaqtlar ===
  jarayonlar ketma-ketdan tez (Queue): False
  jarayonlar ketma-ketdan tez (shared_memory): False
  shared_memory Queue dan tez yoki teng: True
  ⭐ ma'lumot qancha katta bo'lsa, nusxalash narxi shuncha sezilarli

=== 5. Xatolar otaga yetkaziladi ===
  qaytgan xatolar: 2
  birinchisi: bo'lak 0: ValueError: ma'lumot buzilgan
  jarayonlar normal tugadi (exitcode 0): True
  ⭐ xatoni ushlab, natija sifatida qaytarish — ota uni ko'radi va qaror qabul qiladi

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
"Jarayonlar global o'zgaruvchini bo'lishadi" Har birida o'z nusxasi
"if __name__ == '__main__' — uslub masalasi" spawn da majburiy: aks holda jarayonlar ko'payadi
"lambda ni target qilib berish mumkin" pickle qilinmaydi
"Queue ni to'ldirib, keyin join() qilsa bo'ladi" Deadlock: avval get()
"Umumiy xotira bo'lsa, qulf kerak emas" Value uchun ham poyga holati bor
"Manager — tez umumiy lug'at" Har amal IPC — sekin
"terminate() tozalashni bajaradi" Yo'q: finally ishlamaydi
"Bolaning istisnosi otada ko'rinadi" Faqat stderr da; kodga yetkazish uchun Queue yoki pul

6. Keng tarqalgan xatolar va yechimlari

1. __main__ himoyasisiz

python
p = mp.Process(target=f); p.start()      # ❌ spawn: cheksiz ko'payish
if __name__ == "__main__": main()        # ✅

2. lambda yoki ichki funksiya

python
mp.Process(target=lambda: ishla())       # ❌ PicklingError
mp.Process(target=ishla)                 # ✅

3. join() dan oldin navbatni bo'shatmaslik

python
for p in jarayonlar: p.join()            # ❌ navbat to'lgan bo'lsa — deadlock
natijalar = [q.get() for _ in ...]       # ✅ avval get
for p in jarayonlar: p.join()

4. Katta ma'lumotni har vazifaga uzatish

python
ex.map(ishla, [(butun_massiv, i) for i in ...])   # ❌ har safar nusxa
shm = SharedMemory(...)                            # ✅ yoki fayl

5. Qulfsiz Value

python
v.value += 1                             # ❌ poyga holati
with qulf: v.value += 1                  # ✅

6. shared_memory ni tozalamaslik

python
shm = SharedMemory(create=True, size=n)  # ⚠️ unlink qilinmasa, tizimda qoladi
try: ...
finally: shm.close(); shm.unlink()       # ✅

7. Bolaning xatosini e'tiborsiz qoldirish

python
p.join()                                 # ⚠️ exitcode tekshirilmadi
if p.exitcode != 0: qayd_qil(...)        # ✅

8. Jarayonlarni mayda ish uchun ishlatish

python
mp.Process(target=kichik_funksiya)       # ⚠️ yaratish narxi ishdan qimmat (12.6-dars)

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 12.6-dars (o'tilgan): jarayonlarning narxi va bo'lak hajmi
  • 14.1-dars (o'tilgan): modellar taqqoslovi
  • 14.3-dars (o'tilgan): poyga holatlari — jarayonlarda ham bor
  • 14.5-dars: Pool va concurrent.futures — yuqori darajadagi API
  • 15-qism: logging — jarayonlardan log yig'ish
  • 24-qism: NumPy massivlarini shared_memory bilan bo'lishish
  • 28-qism: ishchi jarayonlar va konteynerlar
  • 29-qism: miqyoslash va yuk taqsimlash

8. Eng yaxshi amaliyotlar

  1. if __name__ == "__main__": — har doim.

  2. Target — modul darajasidagi funksiya.

  3. Avval natijalarni get(), keyin join().

  4. Har join va get ga timeout bering.

  5. exitcode ni tekshiring.

  6. Katta ma'lumot — shared_memory yoki fayl orqali.

  7. Manager ni issiq siklda ishlatmang.

  8. Yuqori darajadagi ProcessPoolExecutor ni afzal ko'ring (14.5-dars).


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import multiprocessing as mp
import os
1.  print(mp.cpu_count() >= 1)
2.  print(mp.get_start_method() in {"spawn", "fork", "forkserver"})
3.  print(type(mp.Queue()).__name__)
4.  q = mp.Queue()
    q.put(5)
    print(q.get(timeout=5))
5.  v = mp.Value("i", 7)
    print(v.value)
6.  v.value += 1
    print(v.value)
7.  a = mp.Array("i", [1, 2, 3])
    print(list(a))
8.  p = mp.Process(target=print, args=("salom",))
    print(p.exitcode, p.is_alive())
9.  ota, bola = mp.Pipe()
    ota.send([1, 2])
    print(bola.recv())
10. print(mp.current_process().name)
11. import pickle
    try:
        pickle.dumps(lambda: 1)
    except Exception as e:
        print(type(e).__name__)
12. print(os.getpid() == mp.current_process().pid)
Javoblar
  1. True
  2. True
  3. Queue
  4. 5
  5. 7
  6. 8
  7. [1, 2, 3]
  8. None False — hali ishga tushirilmagan
  9. [1, 2]
  10. MainProcess
  11. PicklingError (yoki AttributeError — versiyaga qarab)
  12. True

Vazifa 2: Xatolarni tuzating

python
1.  # skript.py
    p = mp.Process(target=ishla)
    p.start()
    p.join()

2.  mp.Process(target=lambda x: x * 2, args=(5,)).start()

3.  jarayonlar = [mp.Process(target=ishchi, args=(q,)) for _ in range(4)]
    for p in jarayonlar: p.start()
    for p in jarayonlar: p.join()
    natijalar = [q.get() for _ in range(4)]

4.  v = mp.Value("i", 0)
    def oshir():
        for _ in range(10000):
            v.value += 1

5.  shm = shared_memory.SharedMemory(create=True, size=1_000_000)
    ishla(shm.name)
    # dastur tugaydi
Javoblar
python
1.  def main():
        p = mp.Process(target=ishla)
        p.start()
        p.join(timeout=60)
        if p.exitcode != 0:
            raise RuntimeError(f"ishchi xato bilan tugadi: {p.exitcode}")
    if __name__ == "__main__":          # spawn uchun majburiy
        main()

2.  def ikki_barobar(x): return x * 2   # modul darajasida
    mp.Process(target=ikki_barobar, args=(5,)).start()

3.  for p in jarayonlar: p.start()
    natijalar = [q.get(timeout=60) for _ in range(4)]   # avval get
    for p in jarayonlar: p.join(timeout=60)

4.  qulf = mp.Lock()
    def oshir(v, qulf):
        for _ in range(10000):
            with qulf:
                v.value += 1

5.  try:
        ishla(shm.name)
    finally:
        shm.close()
        shm.unlink()                    # tizimda qolib ketmasin

Vazifa 3: Parallel fayl qayta ishlash

  1. 20 ta katta matn faylini yarating (har biri ~5 MB)
  2. Har fayl uchun: qatorlar soni, so'zlar soni, eng ko'p uchraydigan 5 so'z
  3. Ketma-ket, iplar va jarayonlar bilan bajaring
  4. Vaqt va xotirani solishtiring; natijalarni jadvalda ko'rsating
  5. Bo'lak hajmini o'zgartirib, optimal qiymatni toping (12.6-dars)

Vazifa 4: shared_memory bilan massiv

  1. 50 MB lik array.array yarating va shared_memory ga joylang
  2. 4 jarayon uni bo'laklab qayta ishlasin (masalan, har elementga funksiya qo'llash)
  3. Xuddi shu ishni Queue orqali uzatish bilan bajaring
  4. Vaqt va xotirani solishtiring
  5. Tozalash (close, unlink) to'g'ri bajarilishini tekshiring

Vazifa 5: Ishchi jarayonlar menejeri

JarayonlarMenejeri klassini yozing:

  1. N ta ishchi jarayon, vazifalar Queue orqali
  2. Ishchi yiqilsa — avtomatik qayta ishga tushirish
  3. Xushmuomala to'xtatish: sentinel, keyin terminate, keyin kill (bosqichma-bosqich)
  4. Har ishchining holati: bajarilgan vazifalar, xatolar, ishlash vaqti
  5. Stress-test: 10 000 vazifa, ishchilarning bir qismini ataylab yiqiting

Vazifa 6: Ishga tushirish usullari

Linux mavjud bo'lsa (yoki WSL):

  1. fork, spawn, forkserver bilan bir xil kodni ishga tushiring
  2. Yaratish vaqtini va xotirani o'lchang
  3. fork bilan iplar birga ishlatilganda muammoni ko'rsating
  4. Global holatning nusxalanishi qanday farq qilishini ko'rsating
  5. Qaysi holatda qaysi usulni tanlashni yozing

Vazifa 7: O'ylash

Erlang'da jarayonlar juda arzon (~300 bayt) va til darajasida; Go'da goroutine'lar bitta jarayon ichida; Node.js'da cluster moduli jarayonlarni ko'paytiradi; C/C++ da fork va umumiy xotira bevosita ishlatiladi. Python'ning multiprocessing modeli ular bilan qanday taqqoslanadi va nega "har so'rov uchun jarayon" modeli veb-serverlarda hali ham keng tarqalgan?

Javob

Qisqa javob: Python jarayonlari OT jarayonlari — ular Erlang jarayonlaridan minglab barobar qimmat, lekin to'liq izolyatsiya beradi. Veb-serverlarda (Gunicorn, uWSGI) jarayonlar modeli GIL ni chetlab o'tish va nosozlikka chidamlilik uchun qo'llaniladi: bitta ishchi yiqilsa, qolganlari ishlayveradi.

1. Taqqoslash

Platforma Birlik Narxi Izolyatsiya
Erlang Til jarayoni ~300 bayt To'liq (xabar almashish)
Go Goroutine ~2–8 KB Yo'q (umumiy xotira)
Node.js cluster ishchisi OT jarayoni To'liq
Python OT jarayoni MB lar To'liq
C/C++ fork Copy-on-write To'liq (umumiy xotira ixtiyoriy)

2. Nega veb-serverlarda jarayonlar

  1. GIL ni chetlab o'tish: har jarayon o'z interpretatoriga ega — ko'p yadrodan foydalanish
  2. Nosozlikka chidamlilik: segfault yoki xotira sizishi faqat bitta ishchini o'ldiradi
  3. Oddiylik: ilova kodi konkurentlik haqida o'ylamaydi — har so'rov o'z jarayonida (yoki ipida)
  4. Qayta ishga tushirish: ishchilarni vaqti-vaqti bilan yangilash (xotira sizishiga qarshi klassik usul)

3. Narxi

Narx Tafsilot
Xotira Har ishchi — to'liq interpretator va import qilingan modullar (50–300 MB)
Ishga tushirish Sekin (spawn da modullar qayta import qilinadi)
Ma'lumot almashish pickle, IPC — umumiy kesh qiyin
Sozlash Ishchilar soni: odatda 2 × yadro + 1, lekin xotiraga qarab

4. Zamonaviy yo'nalish

  • Ishchi jarayonlar + ipda/async konkurentlik: Gunicorn + gthread/uvicorn — ikkala darajadan foydalanish
  • Subinterpretatorlar (12.5-dars): jarayonlar izolyatsiyasiga yaqin, lekin arzonroq
  • Erkin ipli Python: kelajakda "bitta jarayon, ko'p ip" modelini jozibador qilishi mumkin
  • Konteynerlar davrida: "bir konteyner — bir jarayon" va miqyoslash orkestrator zimmasida (28-qism)

5. Amaliy xulosa

  1. Python'da jarayon — qimmat, lekin ishonchli birlik; uni ishchi darajasida ishlating, vazifa darajasida emas
  2. Ma'lumotni uzatishni minimallashtiring (shared_memory, fayl, baza)
  3. Nosozlikka chidamlilik kerak bo'lsa — jarayonlar; tezlik kerak bo'lsa — o'lchab tanlang
  4. Erlang saboqi: izolyatsiya + xabar almashish — eng ishonchli konkurentlik modeli

Nimani mustahkamlaydi: 2.1–2.7-bo'limlar.


Xulosa

Bu darsda jarayonlar bilan haqiqiy parallellikni o'rgandik.

Eng muhim uch fikr:

  1. Jarayon — alohida interpretator. Global o'zgaruvchilar nusxalanadi, xotira bo'lishilmaydi, hamma narsa pickle orqali o'tadi. spawn (Windows, macOS) va forkserver (3.14 dan Linux'da sukut) rejimlarida bola modulni qayta import qiladi — shuning uchun ishga tushirish kodi if __name__ == "__main__": ichida bo'lishi shart, target esa modul darajasidagi funksiya (lambda seriyalanmaydi).

  2. Ma'lumot almashish — narx masalasi. Queue va Pipe qulay, lekin nusxalash bor; Value/Array umumiy xotira beradi (va qulf talab qiladi — poyga holati jarayonlarda ham bor); shared_memory katta massivlarni nusxasiz bo'lishadi; Manager esa eng qulay va eng sekin (har amal — jarayonlararo aloqa).

  3. Tartib muhim: get → join → exitcode. Navbatni bo'shatmasdan join() qilish — klassik deadlock. Har join/get ga timeout bering, exitcode ni tekshiring (0 — yaxshi, 1 — istisno, manfiy — signal), bolaning xatosini esa natija sifatida otaga yetkazing — terminate() tozalashni bajarmaydi.

Keyingi darsda yuqori darajadagi API ga o'tamiz: multiprocessing.Pool va concurrent.futures — ular shu darsdagi qo'lda ishlarning ko'pini o'z zimmasiga oladi.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
14.4-dars: multiprocessing — IlmHamroh