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

Threading & GIL

หมายเหตุเกี่ยวกับ Playground: threading ต้องการการสร้าง OS thread — browser sandbox ไม่สามารถทำได้ รัน code snippets ในเครื่องด้วย python3 component <TsGo>, <Quiz> และ <ProgressTracker> ยังคงทำงานได้ตามปกติ

JavaScript ให้คุณ model เดียวสำหรับงาน concurrent I/O: single-threaded event loop กับ async/await สำหรับงาน CPU-heavy คุณ spawn Worker ซึ่ง run บน thread ที่แยกต่างหากโดยสิ้นเชิงพร้อม memory ของตัวเอง — ไม่มี shared state โดย default

threading module ของ Python สร้าง OS thread ที่ แชร์ memory space เดียวกัน ทำให้การส่งข้อมูลระหว่าง thread ง่าย แต่ยังหมายความว่าคุณต้องปกป้อง shared data ด้วย lock เพื่อหลีกเลี่ยง race condition

TypeScript
// TypeScript — Web Worker (separate memory, message passing)
const worker = new Worker(new URL("./worker.ts", import.meta.url));
worker.postMessage({ numbers: [1, 2, 3, 4, 5] });
worker.onmessage = (e) => console.log("result:", e.data);
// Inside worker.ts:
self.onmessage = (e) => {
const sum = e.data.numbers.reduce((a: number, b: number) => a + b, 0);
self.postMessage(sum);
};
Python
# Python — threads share memory directly
import threading
results: list[int] = []
lock = threading.Lock()
def worker(numbers: list[int]) -> None:
total = sum(numbers)
with lock: # protect shared state
results.append(total)
t = threading.Thread(target=worker, args=([1, 2, 3, 4, 5],))
t.start()
t.join() # wait for thread to finish
print(results) # [15]
import threading
import time
def download(url: str) -> None:
print(f"Downloading {url}...")
time.sleep(1) # simulate I/O wait
print(f"Done: {url}")
urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/tags",
]
threads = [threading.Thread(target=download, args=(url,)) for url in urls]
for t in threads:
t.start() # start all threads
for t in threads:
t.join() # wait for all to finish
print("All downloads complete")

รัน code นี้ในเครื่อง — browser sandbox ไม่สามารถ spawn OS thread/process ได้

Thread ไม่ return value โดยตรง (ต่างจาก async/await) pattern ที่พบบ่อยคือใช้ shared list หรือ queue.Queue:

import threading
import queue
def fetch_data(label: str, result_queue: queue.Queue) -> None:
# simulate work
import time; time.sleep(0.1)
result_queue.put(f"data from {label}")
q: queue.Queue[str] = queue.Queue()
threads = [
threading.Thread(target=fetch_data, args=(label, q))
for label in ["users", "posts", "tags"]
]
for t in threads:
t.start()
for t in threads:
t.join()
results = [q.get() for _ in threads]
print(results)
TypeScript
// TypeScript — you rarely think about this distinction because
// JS is always single-threaded. Workers are for any heavy work.
// asyncio/Promises handle all I/O natively.
Python
# Python — choose the right tool
# Use threading when:
# - Wrapping a blocking library that has no async version
# (e.g., an old database driver, a blocking SDK)
# - The task is I/O-bound (file reads, network calls, DB queries)
# - You need shared memory between tasks (vs multiprocessing)
# Prefer asyncio when:
# - You control the code and can use async libraries
# - You need thousands of concurrent I/O tasks
# (threads have OS overhead; coroutines are cheap)
# Use multiprocessing when:
# - The task is CPU-bound (math, image processing, ML inference)
import threading
counter = 0
lock = threading.Lock()
def safe_increment() -> None:
global counter
with lock: # acquire on enter, release on exit (even if exception)
counter += 1
threads = [threading.Thread(target=safe_increment) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
print(counter) # always 100 — lock prevents race condition
GIL ป้องกันอะไรใน CPython?
สำหรับ workload ประเภทใดที่ Python thread ให้ speedup จริงๆ?
Python thread แตกต่างจาก JavaScript Web Workers ในเรื่อง memory อย่างไร?
วิธีที่ถูกต้องในการปกป้อง shared variable ที่หลาย thread เข้าถึงคืออะไร?