Multiprocessing
ข้ามข้อจำกัด GIL ด้วยการแยกเป็นหลาย process — สำหรับงานคำนวณหนัก (CPU-bound)
เมื่องานเป็น CPU-bound (คำนวณหนัก) thread ช่วยไม่ได้เพราะ GIL ทางออกคือ multiprocessing — แยกเป็นหลาย process ที่แต่ละตัวมี Python interpreter ของตัวเอง (มี GIL ของตัวเอง) จึงรันพร้อมกันจริงบนหลาย core
ProcessPoolExecutor
ใช้คล้าย ThreadPoolExecutor แต่เป็น process — เหมาะกับงานคำนวณหนักที่แบ่งเป็นชิ้นได้
from concurrent.futures import ProcessPoolExecutor
def heavy_compute(n):
# งาน CPU-bound: คำนวณหนัก
return sum(i * i for i in range(n))
if __name__ == "__main__": # จำเป็นสำหรับ multiprocessing
tasks = [10_000_000] * 4
with ProcessPoolExecutor() as executor:
results = list(executor.map(heavy_compute, tasks))
print(results)
# ใช้หลาย core พร้อมกันจริง เร็วกว่าทำทีละตัวmultiprocessing ต้องอยู่ใต้ guard if __name__ == "__main__": (จากบท 4) ไม่งั้นบางระบบจะสร้าง process ซ้อนไม่รู้จบ — เป็นกับดักที่เจอบ่อยกับมือใหม่
thread vs process — ต้นทุน
| thread | process | |
|---|---|---|
| memory | แชร์กัน (เบา) | แยกกัน (หนัก) |
| ข้าม GIL | ไม่ได้ | ได้ (พร้อมกันจริง) |
| เหมาะกับ | I/O-bound | CPU-bound |
| ต้นทุนสร้าง | ต่ำ | สูง |
การสร้าง process มีต้นทุนสูงกว่า thread (แยก memory, ส่งข้อมูลข้าม process ต้อง serialize) — ใช้กับงานคำนวณหนักจริง ๆ ที่คุ้มค่า ไม่ใช่งานเล็ก ๆ
สรุปหัวข้อนี้
- multiprocessing แยกเป็นหลาย process ข้าม GIL → รันพร้อมกันจริง
- เหมาะกับ CPU-bound; ใช้ ProcessPoolExecutor คล้าย thread pool
- ต้องมี if __name__ == "__main__" guard
- process หนักกว่า thread (memory แยก, ต้นทุนสูง) — ใช้เมื่อคุ้ม
1) ใช้ ProcessPoolExecutor เร่งงานคำนวณหนักหลายชิ้น เทียบเวลากับแบบลำดับ 2) ลองงานเดียวกันด้วย thread แล้วเทียบ (จะเห็นว่า process เร็วกว่าสำหรับ CPU-bound) 3) อธิบายว่าทำไม process ข้าม GIL ได้ 4) บอกข้อเสียของ process เทียบ thread