Mundarija (21)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Process
- 2.2. Ishga tushirish usullari
- 2.3. pickle talablari
- 2.4. Ma'lumot almashish
- 2.5. Xatolar va to'xtatish
- 2.6. Jarayonlardan log yozish
- 2.7. Qachon jarayon kerak emas
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Process asoslari
- Misol 2 — pickle talablari
- Misol 3 — Ma'lumot almashish
- Misol 4 — Amaliy: parallel qayta ishlash quvuri
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
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:
- Skript cheksiz ko'payib ketdi: Windows'da
if __name__ == "__main__"yozilmagani uchun har bola jarayon modulni qayta import qilib, yana jarayonlar yaratdi - Ishchiga
lambdauzatilgan edi —PicklingError - Natijalar
Queueorqali qaytarilardi, lekin jarayonqueue.get()dan oldinjoin()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,forkserverva__main__qoidasi pickletalablari: 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
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 |
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:
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) |
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/stderriga yozadi — aralashib ketishi mumkin - To'g'ri yechim:
QueueHandler+ otadaQueueListener(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
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, sekin4. Batafsil misollar
Misol 1 — Process asoslari
"""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:
=== 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__' ichidaNima ko'rsatdi: 2.1, 2.2, 2.5-bo'limlar.
Misol 2 — pickle talablari
"""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:
=== 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, FalseNima ko'rsatdi: 2.3-bo'lim.
Misol 3 — Ma'lumot almashish
"""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:
=== 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 ishlatmangNima 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.
"""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:
=== 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 qiladiNima 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
p = mp.Process(target=f); p.start() # ❌ spawn: cheksiz ko'payish
if __name__ == "__main__": main() # ✅2. lambda yoki ichki funksiya
mp.Process(target=lambda: ishla()) # ❌ PicklingError
mp.Process(target=ishla) # ✅3. join() dan oldin navbatni bo'shatmaslik
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
ex.map(ishla, [(butun_massiv, i) for i in ...]) # ❌ har safar nusxa
shm = SharedMemory(...) # ✅ yoki fayl5. Qulfsiz Value
v.value += 1 # ❌ poyga holati
with qulf: v.value += 1 # ✅6. shared_memory ni tozalamaslik
shm = SharedMemory(create=True, size=n) # ⚠️ unlink qilinmasa, tizimda qoladi
try: ...
finally: shm.close(); shm.unlink() # ✅7. Bolaning xatosini e'tiborsiz qoldirish
p.join() # ⚠️ exitcode tekshirilmadi
if p.exitcode != 0: qayd_qil(...) # ✅8. Jarayonlarni mayda ish uchun ishlatish
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:
Poolvaconcurrent.futures— yuqori darajadagi API - 15-qism:
logging— jarayonlardan log yig'ish - 24-qism: NumPy massivlarini
shared_memorybilan bo'lishish - 28-qism: ishchi jarayonlar va konteynerlar
- 29-qism: miqyoslash va yuk taqsimlash
8. Eng yaxshi amaliyotlar
if __name__ == "__main__":— har doim.Target — modul darajasidagi funksiya.
Avval natijalarni
get(), keyinjoin().Har
joinvagetgatimeoutbering.exitcodeni tekshiring.Katta ma'lumot —
shared_memoryyoki fayl orqali.Managerni issiq siklda ishlatmang.Yuqori darajadagi
ProcessPoolExecutorni afzal ko'ring (14.5-dars).
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
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
TrueTrueQueue578[1, 2, 3]None False— hali ishga tushirilmagan[1, 2]MainProcessPicklingError(yokiAttributeError— versiyaga qarab)True
Vazifa 2: Xatolarni tuzating
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 tugaydiJavoblar
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 ketmasinVazifa 3: Parallel fayl qayta ishlash
- 20 ta katta matn faylini yarating (har biri ~5 MB)
- Har fayl uchun: qatorlar soni, so'zlar soni, eng ko'p uchraydigan 5 so'z
- Ketma-ket, iplar va jarayonlar bilan bajaring
- Vaqt va xotirani solishtiring; natijalarni jadvalda ko'rsating
- Bo'lak hajmini o'zgartirib, optimal qiymatni toping (12.6-dars)
Vazifa 4: shared_memory bilan massiv
- 50 MB lik
array.arrayyarating vashared_memoryga joylang - 4 jarayon uni bo'laklab qayta ishlasin (masalan, har elementga funksiya qo'llash)
- Xuddi shu ishni
Queueorqali uzatish bilan bajaring - Vaqt va xotirani solishtiring
- Tozalash (
close,unlink) to'g'ri bajarilishini tekshiring
Vazifa 5: Ishchi jarayonlar menejeri
JarayonlarMenejeri klassini yozing:
Nta ishchi jarayon, vazifalarQueueorqali- Ishchi yiqilsa — avtomatik qayta ishga tushirish
- Xushmuomala to'xtatish: sentinel, keyin
terminate, keyinkill(bosqichma-bosqich) - Har ishchining holati: bajarilgan vazifalar, xatolar, ishlash vaqti
- Stress-test: 10 000 vazifa, ishchilarning bir qismini ataylab yiqiting
Vazifa 6: Ishga tushirish usullari
Linux mavjud bo'lsa (yoki WSL):
fork,spawn,forkserverbilan bir xil kodni ishga tushiring- Yaratish vaqtini va xotirani o'lchang
forkbilan iplar birga ishlatilganda muammoni ko'rsating- Global holatning nusxalanishi qanday farq qilishini ko'rsating
- 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
- GIL ni chetlab o'tish: har jarayon o'z interpretatoriga ega — ko'p yadrodan foydalanish
- Nosozlikka chidamlilik: segfault yoki xotira sizishi faqat bitta ishchini o'ldiradi
- Oddiylik: ilova kodi konkurentlik haqida o'ylamaydi — har so'rov o'z jarayonida (yoki ipida)
- 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
- Python'da jarayon — qimmat, lekin ishonchli birlik; uni ishchi darajasida ishlating, vazifa darajasida emas
- Ma'lumotni uzatishni minimallashtiring (
shared_memory, fayl, baza) - Nosozlikka chidamlilik kerak bo'lsa — jarayonlar; tezlik kerak bo'lsa — o'lchab tanlang
- 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:
Jarayon — alohida interpretator. Global o'zgaruvchilar nusxalanadi, xotira bo'lishilmaydi, hamma narsa
pickleorqali o'tadi.spawn(Windows, macOS) vaforkserver(3.14 dan Linux'da sukut) rejimlarida bola modulni qayta import qiladi — shuning uchun ishga tushirish kodiif __name__ == "__main__":ichida bo'lishi shart,targetesa modul darajasidagi funksiya (lambdaseriyalanmaydi).Ma'lumot almashish — narx masalasi.
QueuevaPipequlay, lekin nusxalash bor;Value/Arrayumumiy xotira beradi (va qulf talab qiladi — poyga holati jarayonlarda ham bor);shared_memorykatta massivlarni nusxasiz bo'lishadi;Manageresa eng qulay va eng sekin (har amal — jarayonlararo aloqa).Tartib muhim:
get→join→exitcode. Navbatni bo'shatmasdanjoin()qilish — klassik deadlock. Harjoin/getgatimeoutbering,exitcodeni 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.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!