Global Interpreter Lock (GIL) เป็น Mutex (Mutual Exclusion Lock) ที่อนุญาตให้มีเพียง หนึ่ง Thread** เท่านั้น ที่จะสามารถควบคุม Python Interpreter ได้ในเวลาเดียวกัน แม้ในระบบ Multi-core CPU ก็ตาม ทำให้ Python ไม่สามารถทำ True Parallelism ด้วย Threads ได้สำหรับ CPU-bound Tasks
ทำไม Python ถึงมี GIL?
1. Memory Management: Reference Counting
Python ใช้ Reference Counting สำหรับการจัดการหน่วยความจำ อ็อบเจ็กต์แต่ละตัวมีตัวนับอ้างอิง (Reference Count) เมื่อค่าลดเหลือ 0 หน่วยความจำจะถูกปล่อยทันที
import sys
var = {}
print(sys.getrefcount(var)) # 2 (ตัวแปร var + พารามิเตอร์ฟังก์ชัน)
v = var
print(sys.getrefcount(v)) # 3 (var + v + พารามิเตอร์)
2. ปัญหา Race Condition
ถ้ามีหลาย Thread เพิ่ม/ลด Reference Count พร้อมกันโดยไม่มี Lock อาจเกิด:
- Memory Leaks (ไม่ปล่อยหน่วยความจำ)
- Use-after-free (เข้าถึงหน่วยความจำที่ปล่อยแล้ว)
- Double Free (ปล่อยหน่วยความจำซ้ำ)
3. ทำไมไม่ใช้ Lock ต่ออ็อบเจ็กต์ (Fine-grained Locking)?
- Overhead สูงมาก (Lock/Unlock ทุกครั้งที่เข้าถึงอ็อบเจ็กต์)
- เสี่ยงต่อ Deadlock ได้ง่าย
- ซับซ้อนในการ Implement
GIL จึงเป็น Coarse-grained Lock แบบง่ายและปลอดภัยกว่า
ผลกระทบต่อประสิทธิภาพ
CPU-bound Tasks (คำนวณหนัก)
import threading
import time
def cpu_bound(n):
while n > 0:
n -= 1
# Single thread
start = time.time()
cpu_bound(100_000_000)
print(f"Single thread: {time.time() - start:.2f}s")
# Multi-thread (ยังช้ากว่าหรือเท่าเดิมเนื่องจาก GIL)
threads = [threading.Thread(target=cpu_bound, args=(25_000_000,)) for _ in range(4)]
start = time.time()
for t in threads: t.start()
for t in threads: t.join()
print(f"Multi-thread: {time.time() - start:.2f}s")
ผลลัพธ์ทั่วไป: Multi-thread ช้ากว่าหรือเท่า Single thread เนื่องจาก GIL Contention
I/O-bound Tasks (Network, Disk, Database)
GIL จะถูกปล่อย (Released) ระหว่าง I/O Operations ดังนั้น Threads จึงทำงาน併行ได้จริง:
import threading
import requests
import time
def fetch_url(url):
requests.get(url)
urls = ['https://httpbin.org/delay/1'] * 4
# Multi-thread สำหรับ I/O - เร็วกว่ามาก
threads = [threading.Thread(target=fetch_url, args=(u,)) for u in urls]
start = time.time()
for t in threads: t.start()
for t in threads: t.join()
print(f"Multi-thread I/O: {time.time() - start:.2f}s") # ~1 วินาที ไม่ใช่ 4 วินาที
วิธีหลีกเลี่ยง GIL
| วิธี | เหมาะสำหรับ | ความซับซ้อน |
|---|---|---|
| Multiprocessing (แยก Process) | CPU-bound Tasks | ปานกลาง |
| C Extensions (Cython, C API) | Performance Critical | สูง |
| Asyncio (Async/Await) | I/O-bound, High Concurrency | ปานกลาง |
| Alternative Interpreters (PyPy, GraalPy) | บาง Workloads | ต่ำ (แค่เปลี่ยน Interpreter) |
| Thread Pool + C Libraries (NumPy, Pandas) | Data Processing | ต่ำ |
ตัวอย่าง: ใช้ Multiprocessing แทน Threading
from multiprocessing import Pool
import time
def cpu_bound(n):
while n > 0:
n -= 1
return n
if __name__ == '__main__':
with Pool(4) as p:
start = time.time()
p.map(cpu_bound, [25_000_000] * 4)
print(f"Multiprocessing: {time.time() - start:.2f}s")
อนาคตของ GIL
Python 3.12+ เริ่มมีการพัฒนา Sub-interpreters (PEP 554) ที่แต่ละอินเตอร์พรีเตอร์จะมี GIL ของตัวเอง และ Python 3.13+ มี Experimental No-GIL Build (PEP 703) ที่ลบ GIL ออกได้ แต่นายังอยู่ในช่วงทดลองและอาจมีผลกระทบต่อ Single-thread Performance
สรุป
- GIL เป็นข้อจำกัดของ CPython (Standard Implementation)
- ส่งผลต่อ CPU-bound Multi-threading เท่านั้น
- I/O-bound Tasks ไม่ได้รับผลกระทบมาก
- ใช้ Multiprocessing หรือ Asyncio แทนสำหรับ Parallelism
- ไลบรารี Data Science (NumPy, Pandas, TensorFlow) หลุดพ้น GIL ได้เองด้วย C Extensions