「Python 多線程是雞肋,請用多進程」——這句話在社區流傳甚廣。本文從 GIL 設計初衷、運行機制、Python 2/3 差異、CPU/IO 密集型表現差異、多核下的線程顫簸現象,系統論證為何多進程才是 Python 利用多核的正解。
while (true) {
acquire(GIL); // 1. 拿到「通行證」
run_bytecode(ticks); // 2. 執行字節碼(直到 IO / 時間片用盡)
release(GIL); // 3. 歸還鎖,喚醒其他線程
}
關鍵點:GIL 是**進程級**的,每個進程獨立擁有一把,進程間互不干擾。
| 版本 | 釋放觸發條件 | 特點 |
|---|---|---|
| Python 2.x | 遇到 IO 操作 **或** ticks == 100(可通過 sys.setcheckinterval 調整) | 固定計數,CPU 密集型頻繁切換開銷大 |
| Python 3.2+ | 遇到 IO 操作 **或** 運行時間達閾值(默認 5ms,可通過 sys.setswitchinterval 調整) | 基於時間片,對 CPU 密集型更友好,但**未解決單核並行本質** |
| 類型 | 典型場景 | 多線程效果 | 原因 |
|---|---|---|---|
| CPU 密集型 | 圖像處理、科學計算、大數據排序、加密解密 | **負優化**(比單線程慢) | 頻繁搶鎖/切換開銷 > 並行收益;GIL 迫使單核串行 |
| IO 密集型 | 網絡爬蟲、文件讀寫、數據庫查詢、API 調用 | **顯著提升** | IO 等待期間釋放 GIL,其他線程可執行,實現併發 |
實測結論:CPU 核心數越多,GIL 爭用越劇烈,多線程性能**遞減**。
multiprocessing.Process / Pool API 與 threading 極其相似,遷移成本低from multiprocessing import Pool
def cpu_heavy(x):
return sum(i * i for i in range(x))
if __name__ == '__main__':
with Pool(4) as p: # 4 進程 = 4 核並行
print(p.map(cpu_heavy, [10**6]*4))
Py_BEGIN_ALLOW_THREADS,實現多線程並行multiprocessing.shared_memory (Py3.8+) 降低 IPC 開銷
任務類型
├─ CPU 密集型
│ ├─ 純 Python → multiprocessing / ProcessPoolExecutor
│ └─ 可用 C 庫 (NumPy等) → 多線程(庫內釋放 GIL)
└─ IO 密集型
├─ 高併發、長連接 → asyncio / aiohttp
├─ 簡單阻塞 IO → threading / ThreadPoolExecutor
└─ 需繞過 GIL 做並行計算 → multiprocessing