Python3 多线程

What the GIL really blocks, when threads still help, and how to share data safely.

Intermediate
Key PointsThe GIL serialises bytecode execution, so threads speed up I/O-bound work, not CPU-bound work — use processes for the latter.

What the GIL Is

CPython has a global interpreter lock: only one thread executes Python bytecode at a time. Threads still help because most real work waits — on sockets, disks, databases — and the GIL is released during those waits.

WorkloadThreadsProcessesWhy
HTTP / DB callsYesOverkillWaiting releases the GIL
File / disk I/OYesOverkillSame reason
Pure-Python CPU loopsNoYesGIL serialises bytecode
NumPy / C extensionsPartlyYesC code may release the GIL

Thread Basics

Python
import threading
import time


def fetch(url):
    time.sleep(0.5)                  # 模拟网络等待
    print("done", url)


threads = [threading.Thread(target=fetch, args=(f"u{i}",)) for i in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()                          # 等全部结束

ThreadPoolExecutor

Prefer the executor API: it manages worker lifecycle, returns futures, and propagates exceptions. Do not spawn hundreds of raw threads.

Python
from concurrent.futures import ThreadPoolExecutor, as_completed

urls = [f"https://api.example.com/{i}" for i in range(10)]

with ThreadPoolExecutor(max_workers=8) as pool:
    futures = {pool.submit(fetch, u): u for u in urls}
    for fut in as_completed(futures):
        url = futures[fut]
        try:
            fut.result()
        except Exception as exc:
            print("failed", url, exc)

Races and Locks

Python
import threading

counter = 0
lock = threading.Lock()


def bump():
    global counter
    for _ in range(100_000):
        with lock:                   # 没有锁,结果会小于 200000
            counter += 1


ts = [threading.Thread(target=bump) for _ in range(2)]
[t.start() for t in ts]
[t.join() for t in ts]
print(counter)

Producer / Consumer with queue

Python
import queue
import threading

q = queue.Queue(maxsize=100)


def producer():
    for i in range(5):
        q.put(i)
    q.put(None)                       # 结束哨兵


def consumer():
    while (item := q.get()) is not None:
        print("got", item)
        q.task_done()


threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()
queue.Queue is already thread-safe — do not wrap it in another lock. For CPU-bound work use ProcessPoolExecutor, or you will be slower than single-threaded.

Friends & Partners