Python 並發程式設計 (一):Threading vs Multiprocessing (第 22 章)

站主自己的課程,請大家支持
揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站? 揭秘站長的架站心法:如何利用 Hugo × AI 打造高質感個人品牌網站?
  • Post by
  • Jan 05, 2024
post-thumb

想要讓程式跑得更快,大家直覺都會想到:「讓它同時做很多事!」(並發)。但在 Python 中,這件事比其他語言稍微複雜一點,因為有一個著名的大魔王——GIL (Global Interpreter Lock,全域直譯器鎖)

1. 什麼是 GIL?

GIL 是 CPython (標準 Python 直譯器) 的一個機制,它確保同一時間只有一個執行緒 (Thread) 在執行 Python Bytecode

這意味著:即使你的電腦有 16 核心 CPU,Python 的多執行緒程式在任何瞬間其實只在運用 1 個核心

那多執行緒在 Python 中是不是廢了?不!這取決於你的任務類型:

  • I/O 密集型 (I/O-bound):爬蟲、讀寫檔案、資料庫。等待硬碟或網路回應時,執行緒會釋放 GIL,適合用 Threading
  • CPU 密集型 (CPU-bound):影像處理、加密運算、矩陣計算。一直佔用 CPU,Threading 反而因為切換 Context 變慢,必須用 Multiprocessing

2. Threading:適合 I/O 任務

模擬一個 I/O 任務 (例如下載網頁)。

import threading
import time

def download_page(url):
    print(f"開始下載 {url}...")
    time.sleep(1) # 模擬網路延遲
    print(f"下載完成 {url}")

start = time.time()

threads = []
urls = ["Page1", "Page2", "Page3"]

for url in urls:
    t = threading.Thread(target=download_page, args=(url,))
    threads.append(t)
    t.start()

# 等待所有執行緒完成
for t in threads:
    t.join()

end = time.time()
print(f"總花費時間: {end - start:.2f} 秒")

執行結果約為 1.0x 秒。如果是依序執行,則需要 3 秒。Threading 在這裡大獲全勝!

3. Multiprocessing:適合 CPU 任務

模擬一個 CPU 運算任務。

import time
from multiprocessing import Process

def heavy_computation(n):
    # 瘋狂運算,吃滿 CPU
    count = 0
    for i in range(n):
        count += 1

N = 50000000

if __name__ == '__main__': # Windows/Mac 上必須加這行保護
    start = time.time()

    p1 = Process(target=heavy_computation, args=(N,))
    p2 = Process(target=heavy_computation, args=(N,))

    p1.start()
    p2.start()

    p1.join()
    p2.join()

    end = time.time()
    print(f"多進程花費時間: {end - start:.2f} 秒")

Multiprocessing 會啟動獨立的 Python 進程 (Process),每個進程有自己的 Python 直譯器和記憶體空間,因此不受 GIL 限制,能真正利用多核心。

如果是用 threading 跑這個 CPU 任務,時間反而會比單執行緒還慢 (因為 GIL 爭奪戰)!

4. concurrent.futures:現代化的寫法

Python 3.2+ 提供了更高階的介面 ThreadPoolExecutorProcessPoolExecutor,寫法更簡單統一。

from concurrent.futures import ThreadPoolExecutor

urls = ["Page1", "Page2", "Page3"]

def download_page(url):
    time.sleep(1)
    return f"{url} Data"

with ThreadPoolExecutor(max_workers=3) as executor:
    # map 會自動分配任務並收集結果
    results = executor.map(download_page, urls)

for res in results:
    print(res)

5. 總結

特性Threading (多執行緒)Multiprocessing (多進程)
記憶體共享 (省記憶體)獨立 (耗記憶體)
GIL 影響受限制不受限制
適用場景I/O 密集型 (爬蟲、Web)CPU 密集型 (AI、計算)
切換成本

下一章,我們將介紹 Python 並發程式設計的最新潮流——AsyncIO (非同步 I/O),這是寫出高效能 Web Server (如 FastAPI) 的基礎!


延伸閱讀

LATEST POST
TAG