Threading & GIL
ทำหลายงาน I/O พร้อมกันด้วย thread — และเข้าใจว่าทำไม GIL ทำให้ thread ไม่เร่งงานคำนวณ
thread คือสายการทำงานย่อยภายในโปรแกรมเดียว ใช้ทำหลายงานสลับกันได้ เหมาะกับงาน I/O-bound แต่มีข้อจำกัดสำคัญใน Python ที่ชื่อ GIL ที่ต้องเข้าใจ
ThreadPoolExecutor — วิธีที่ใช้ง่ายสุด
แทนการสร้าง thread เอง ใช้ ThreadPoolExecutor จัดการ pool ของ thread ให้ — เหมาะกับงาน I/O หลายชิ้น เช่นดาวน์โหลดหลายไฟล์
from concurrent.futures import ThreadPoolExecutor
import time
def download(url):
time.sleep(1) # จำลองรอ network (I/O)
return f"เสร็จ {url}"
urls = ["a", "b", "c", "d"]
# แบบลำดับ: ~4 วินาที
# for u in urls: download(u)
# แบบ thread: ~1 วินาที (รอพร้อมกัน)
with ThreadPoolExecutor(max_workers=4) as executor:
results = list(executor.map(download, urls))
print(results)GIL — Global Interpreter Lock
Python (CPython) มี GIL ที่อนุญาตให้รันโค้ด Python ได้ทีละ thread เท่านั้น ณ เวลาหนึ่ง — แปลว่า thread ไม่ช่วยเร่งงาน CPU-bound (คำนวณหนัก) เพราะถึงมีหลาย thread ก็รันโค้ดทีละตัวอยู่ดี
thread เร่งได้เฉพาะงาน I/O-bound เพราะระหว่างที่ thread หนึ่ง "รอ" network/ดิสก์ GIL จะถูกปล่อยให้ thread อื่นทำงาน แต่ถ้าทุก thread เอาแต่คำนวณ (ไม่รอ) GIL จะกั๊กให้ทำทีละตัว — งานคำนวณหนักต้องใช้ multiprocessing (หัวข้อถัดไป)
สรุปหัวข้อนี้
- thread = สายงานย่อยในโปรแกรมเดียว; ThreadPoolExecutor ใช้ง่ายสุด
- เหมาะกับงาน I/O-bound (รอ network/ดิสก์) — เร่งได้จริง
- GIL: Python รันโค้ดทีละ thread → thread ไม่ช่วย CPU-bound
- งานคำนวณหนักให้ใช้ multiprocessing แทน
1) ใช้ ThreadPoolExecutor เร่งงานจำลอง I/O (time.sleep) หลายชิ้น เทียบเวลากับแบบลำดับ 2) ลองใช้ thread กับงานคำนวณหนักแล้วสังเกตว่าไม่เร็วขึ้น 3) อธิบายว่า GIL คืออะไร 4) บอกว่าทำไม thread ช่วย I/O แต่ไม่ช่วย CPU