Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Python

Python GIL (Global Interpreter Lock) คืออะไร? ทำไมจึงสำคัญ?

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