本文為 Python API 優化基礎 系列文,第 5 篇:
在開發應用程式時,我們經常會遇到兩種類型的運算瓶頸:
- CPU 運算(CPU-Bound):程式的執行速度受限於 CPU 的計算能力,通常出現在數據運算、機器學習、影像處理等場景。
- I/O 操作(I/O-Bound):程式的執行速度受限於 外部設備(如磁碟、網路、資料庫)的存取速度,常見於API 請求、檔案讀寫、資料庫查詢。
根據運算類型的不同,我們可以選擇適合的並行方式來提高效能:
- 對於 I/O 密集型(I/O-Bound)任務,適合使用多執行緒(Threading)。
- 對於 CPU 密集型(CPU-Bound)任務,適合使用多進程(Multiprocessing)。
CPU 運算 vs. I/O 操作
什麼是 CPU 運算(CPU-Bound)?
CPU 運算(CPU-Bound) 指的是程式的執行速度受限於 CPU 的運算能力,這類運算通常需要大量計算,而非等待外部設備的回應。例如:
- 機器學習與深度學習訓練
- 影像處理(如 Photoshop 濾鏡應用)
- 數學計算(如矩陣運算、大量迴圈)
- 數據分析與統計運算
📌 這類任務通常需要強大的 CPU,多執行緒無法有效提升效能,應該使用「多進程(Multiprocessing)」來充分利用多核心 CPU。
什麼是 I/O 操作(I/O-Bound)?
I/O 操作(I/O-Bound) 指的是程式的執行速度受限於外部設備(如網路、硬碟、資料庫)的存取速度,而非 CPU 計算能力。例如:
- 讀取或寫入檔案
- 發送 API 請求
- 存取資料庫
- 網頁爬蟲
📌 這類任務的瓶頸是「等待」,CPU 在等待 I/O 回應時大部分時間是閒置的,因此適合使用「多執行緒(Threading)」來同時執行多個 I/O 任務,提高處理效率。
執行緒(Thread) vs. 進程(Process)
執行緒(Thread)是什麼?
執行緒(Thread) 是程式執行的最小單位,一個程式(進程)可以擁有多個執行緒。
這些執行緒共享相同的記憶體空間,因此可以快速交換數據,適合用於I/O 密集型工作。
📝 主要特點
- 同一進程內的多個執行緒共享記憶體
- 建立與切換成本較低
- 受 Python GIL(Global Interpreter Lock)限制,只能同時運行一個 Python 指令
🔹 適用場景
- API 請求:同時向多個伺服器發送請求,提高數據抓取速度
- 檔案讀取:同時讀取多個日誌文件,進行數據分析
- 網路爬蟲:爬取多個網站,提高效率
進程(Process)是什麼?
進程(Process) 是程式執行的基本單位,每個進程擁有獨立的記憶體空間。不同於執行緒,進程之間不共享記憶體,因此適合CPU 密集型工作。
📝 主要特點
- 每個進程擁有獨立記憶體空間
- 適合 CPU 密集型工作,不受 GIL 限制
- 建立與切換成本較高
🔹 適用場景
- 機器學習模型訓練:使用多核心 CPU 訓練深度學習模型
- 影像處理:批量轉換圖片格式,應用濾鏡
- 大規模數據運算:對上億筆數據進行計算分析
ThreadPoolExecutor vs. ProcessPoolExecutor
Python 提供 ThreadPoolExecutor 和 ProcessPoolExecutor 來分別處理I/O 密集型和CPU 密集型任務。
| 功能 | ThreadPoolExecutor | ProcessPoolExecutor |
|---|---|---|
| 適用場景 | I/O 密集型(API 請求、爬蟲、檔案讀寫) | CPU 密集型(數據計算、影像處理) |
| 資源使用 | 多執行緒(共享記憶體) | 多進程(獨立記憶體) |
| 是否受 GIL 限制 | 是(Python GIL 只允許單執行緒運行 Python 指令) | 否(每個進程有獨立 Python 解釋器) |
| 開銷 | 低(執行緒切換快) | 高(需要額外的記憶體空間) |
Python 並行處理示例
使用 ThreadPoolExecutor 處理 I/O 任務
from concurrent.futures import ThreadPoolExecutor
import requests
URLS = ["https://jsonplaceholder.typicode.com/todos/1",
"https://jsonplaceholder.typicode.com/todos/2"]
def fetch_data(url):
response = requests.get(url)
return response.json()
with ThreadPoolExecutor(max_workers=2) as executor:
results = list(executor.map(fetch_data, URLS))
print(results) # 同時發送 API 請求
📌 適合等待網路回應的場景,如 API 請求或爬蟲。
使用 ProcessPoolExecutor 處理 CPU 計算
from concurrent.futures import ProcessPoolExecutor
def compute(n):
return sum(i**2 for i in range(n))
numbers = [10**6, 10**6, 10**6]
with ProcessPoolExecutor(max_workers=3) as executor:
results = list(executor.map(compute, numbers))
print(results) # 使用多核心 CPU 計算
📌 適合數據計算或影像處理,能真正利用多核心 CPU。
結論
✅ I/O 操作(I/O-Bound)受限於外部設備存取速度,適合用 ThreadPoolExecutor(多執行緒)。
✅ CPU 運算(CPU-Bound)受限於 CPU 計算能力,適合用 ProcessPoolExecutor(多進程)。
✅ Python GIL 限制多執行緒的效能,因此 CPU 密集型工作應使用多進程來提升效能。
🚀 選擇適合的並行處理方式,能顯著提升 Python 程式的執行效率!