Async & Concurrency
เครื่องมือสามตัว สำหรับสามปัญหา
หัวข้อที่มีชื่อว่า “เครื่องมือสามตัว สำหรับสามปัญหา”Python มีโมเดล concurrency ที่แตกต่างกันถึงสามแบบ แต่ละแบบเหมาะกับประเภทของงานที่ต่างกัน:
| โมเดล | เหมาะกับ | กลไก |
|---|---|---|
| asyncio | งาน I/O-bound (เครือข่าย, ไฟล์, DB) | Single thread, cooperative multitasking |
| threading | งาน I/O-bound ที่ใช้ไลบรารีแบบ blocking | OS threads, ถูกจำกัดด้วย GIL |
| multiprocessing | งาน CPU-bound (การคำนวณ, การแยกวิเคราะห์) | Separate processes, parallelism จริง |
การเลือกโมเดลผิดเป็นสาเหตุของบั๊กด้านประสิทธิภาพที่พบบ่อยที่สุดใน Python
การเรียก database แบบ blocking ใน threading.Thread อาจดูปกติ แต่การเรียกแบบเดียวกันนั้นใน asyncio coroutine จะทำให้ event loop ทั้งหมดหยุดทำงาน
เหตุใด asyncio จึงครองตลาด Python ยุคใหม่
หัวข้อที่มีชื่อว่า “เหตุใด asyncio จึงครองตลาด Python ยุคใหม่”งานฝั่งเซิร์ฟเวอร์ส่วนใหญ่เป็น 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 วินาที
GIL และ Threads
หัวข้อที่มีชื่อว่า “GIL และ Threads”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 เดียวเพราะ GILt1 = 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 สำหรับ parallelism จริง
หัวข้อที่มีชื่อว่า “Multiprocessing สำหรับ parallelism จริง”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 Basics | Event loop, async def, await, asyncio.run, asyncio.gather |
| Async / Await | Tasks, create_task, การทำงานแบบ sequential vs concurrent |
| GIL & Threads | GIL, threading, ThreadPoolExecutor, I/O vs CPU-bound |
| Multiprocessing | Processes vs threads, ProcessPoolExecutor, Pool.map |
แต่ละบทเรียนสามารถเรียนได้อิสระ เริ่มด้วย asyncio หากสร้าง network services หรือข้ามไปที่ multiprocessing หากต้องการ parallelize งาน CPU-bound