ข้ามไปยังเนื้อหา

Async & Concurrency

Python มีโมเดล concurrency ที่แตกต่างกันถึงสามแบบ แต่ละแบบเหมาะกับประเภทของงานที่ต่างกัน:

โมเดลเหมาะกับกลไก
asyncioงาน I/O-bound (เครือข่าย, ไฟล์, DB)Single thread, cooperative multitasking
threadingงาน I/O-bound ที่ใช้ไลบรารีแบบ blockingOS threads, ถูกจำกัดด้วย GIL
multiprocessingงาน CPU-bound (การคำนวณ, การแยกวิเคราะห์)Separate processes, parallelism จริง

การเลือกโมเดลผิดเป็นสาเหตุของบั๊กด้านประสิทธิภาพที่พบบ่อยที่สุดใน Python การเรียก database แบบ blocking ใน threading.Thread อาจดูปกติ แต่การเรียกแบบเดียวกันนั้นใน asyncio coroutine จะทำให้ event loop ทั้งหมดหยุดทำงาน

งานฝั่งเซิร์ฟเวอร์ส่วนใหญ่เป็น I/O-bound: รอ database, HTTP API, หรือการอ่านไฟล์ asyncio จัดการการรอพร้อมกันหลายพันรายการใน OS thread เดียว โดยรัน event loop ที่สลับการควบคุมระหว่าง coroutines เมื่อตัวใดตัวหนึ่งรอ I/O

import asyncio
async def fetch(url: str) -> str:
await asyncio.sleep(0.1) # จำลองความล่าช้าของเครือข่าย
return f"Response from {url}"
async def main():
results = await asyncio.gather(
fetch("https://api.example.com/a"),
fetch("https://api.example.com/b"),
)
for r in results:
print(r)
asyncio.run(main())

การเรียกทั้งสองทำงานพร้อมกัน — ไม่มีตัวไหนบล็อกตัวอื่น เวลารวมประมาณ 0.1 วินาที ไม่ใช่ 0.2 วินาที

CPython มี mutex ที่เรียกว่า Global Interpreter Lock (GIL) ซึ่งอนุญาตให้ thread เดียวเรียกใช้ Python bytecode ได้ในช่วงเวลาใดเวลาหนึ่ง สำหรับงาน I/O-bound นี้ไม่ใช่ปัญหา — threads จะปล่อย GIL ขณะรอ OS — แต่สำหรับงาน CPU-bound การเพิ่ม threads ไม่ได้ให้ความเร็วเพิ่มขึ้นเลย

import threading
def count_down(n: int) -> None:
while n > 0:
n -= 1
# สอง threads — ยังคงรันบน core เดียวเพราะ GIL
t1 = threading.Thread(target=count_down, args=(5_000_000,))
t2 = threading.Thread(target=count_down, args=(5_000_000,))
t1.start(); t2.start()
t1.join(); t2.join()

multiprocessing สร้าง OS processes แยกกัน แต่ละ process มี Python interpreter และ GIL ของตัวเอง งาน CPU-bound จึงสามารถใช้ทุก core ที่มีได้

from multiprocessing import Pool
def square(x: int) -> int:
return x * x
with Pool(processes=4) as pool:
results = pool.map(square, range(10))
print(results)
บทเรียนแนวคิดหลัก
asyncio BasicsEvent loop, async def, await, asyncio.run, asyncio.gather
Async / AwaitTasks, create_task, การทำงานแบบ sequential vs concurrent
GIL & ThreadsGIL, threading, ThreadPoolExecutor, I/O vs CPU-bound
MultiprocessingProcesses vs threads, ProcessPoolExecutor, Pool.map

แต่ละบทเรียนสามารถเรียนได้อิสระ เริ่มด้วย asyncio หากสร้าง network services หรือข้ามไปที่ multiprocessing หากต้องการ parallelize งาน CPU-bound

โมเดล concurrency ใดของ Python เหมาะที่สุดสำหรับงาน CPU-bound เช่น การคำนวณเชิงตัวเลข?
เหตุใด asyncio จึงจัดการการรอ I/O พร้อมกันหลายพันรายการได้อย่างมีประสิทธิภาพใน single thread?
Global Interpreter Lock (GIL) คืออะไร?
ข้อความใดเกี่ยวกับ threading และงาน CPU-bound ใน Python ที่ถูกต้อง?