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

GIL — Global Interpreter Lock

JavaScript ทำงานใน event loop single-threaded โดยการออกแบบ ความสามารถด้าน concurrency ทั้งหมดมาจาก asynchrony (async/await, Promise, callback) ไม่ใช่การประมวลผลพร้อมกันแบบขนาน ถ้าคุณต้องการใช้ CPU หลายตัวจริง ๆ คุณต้องใช้ worker_threads

Python มี thread แต่ CPython (implementation หลัก) มี lock ชื่อว่า Global Interpreter Lock (GIL) ที่อนุญาตให้ Python bytecode ทำงานได้ครั้งละหนึ่ง thread เท่านั้น ซึ่งหมายความว่า thread ของ Python ใน CPython ไม่สามารถรัน CPU-bound code แบบขนานได้จริง ๆ แม้บนเครื่องที่มีหลาย CPU

Python ปล่อย GIL ออกระหว่างทำ I/O, ระหว่าง time.sleep() และระหว่างงานใน C extension บางตัวอย่าง NumPy ดังนั้นถ้าเป็น I/O-bound concurrency เช่น เรียก network, อ่านไฟล์ หรือ query database thread ของ Python ทำงานได้ดีมาก

TypeScript
// TypeScript: worker_threads for CPU-bound parallelism
import { Worker, isMainThread, parentPort } from "worker_threads";
if (isMainThread) {
const worker = new Worker(__filename);
worker.on("message", (result) => {
console.log("result from worker:", result);
});
worker.postMessage("start");
} else {
parentPort?.on("message", () => {
// CPU-bound work runs in a real OS thread
let total = 0;
for (let i = 0; i < 1_000_000; i++) total += i;
parentPort?.postMessage(total);
});
}
Python
# Python: threading is effective for I/O-bound work
# but the GIL limits CPU-bound parallelism in CPython
import threading
results = {}
def fetch_data(key, value):
# Simulate I/O work (GIL is released during real I/O)
import time
time.sleep(0.1)
results[key] = value * 2
threads = [
threading.Thread(target=fetch_data, args=(i, i))
for i in range(5)
]
for t in threads:
t.start()
for t in threads:
t.join()
print(results) # {0: 0, 1: 2, 2: 4, 3: 6, 4: 8}

สำหรับงาน CPU-bound ที่ต้องการใช้ CPU หลายตัวจริง ๆ Python มี module multiprocessing ซึ่งแต่ละ worker จะเป็น OS process แยกต่างหากพร้อม Python interpreter ของตัวเอง ทำให้แต่ละ process มี GIL ของตัวเองด้วย

TypeScript
// TypeScript: CPU parallelism via worker_threads pool
import { Worker } from "worker_threads";
import * as os from "os";
function runWorker(data: number): Promise<number> {
return new Promise((resolve, reject) => {
const w = new Worker(`
const { parentPort, workerData } = require("worker_threads");
let total = 0;
for (let i = 0; i < workerData; i++) total += i;
parentPort.postMessage(total);
`, { eval: true, workerData: data });
w.on("message", resolve);
w.on("error", reject);
});
}
const cpus = os.cpus().length;
Promise.all(Array.from({ length: cpus }, (_, i) =>
runWorker(1_000_000 / cpus)
)).then(results => console.log(results.reduce((a, b) => a + b, 0)));
Python
# Python: multiprocessing bypasses the GIL entirely
from multiprocessing import Pool
import os
def cpu_work(n):
return sum(range(n))
if __name__ == "__main__":
cpus = os.cpu_count()
chunk = 1_000_000 // cpus
with Pool(cpus) as pool:
results = pool.map(cpu_work, [chunk] * cpus)
print(sum(results))

หมายเหตุ: Playground ไม่พร้อมใช้งานสำหรับบทเรียนนี้ เนื่องจากตัวอย่าง multiprocessing ต้องการ if __name__ == "__main__" guard และรันได้ถูกต้องเฉพาะในสภาพแวดล้อม process แบบ fork เท่านั้น

GIL ใน CPython ทำอะไร?
threading ของ Python ให้ประโยชน์จริงสำหรับ workload ประเภทใด?
วิธีใดที่ Python แนะนำสำหรับ CPU-bound parallelism จริง ๆ?
Python version ใดที่แนะนำ free-threaded mode (GIL optional) เป็น experimental?