CPU 運算 vs. I/O 操作:執行緒與進程的最佳實踐

Published February 13, 2025 by 徐培鈞
Python

在開發應用程式時,我們經常會遇到兩種類型的運算瓶頸:

  • 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 提供 ThreadPoolExecutorProcessPoolExecutor 來分別處理I/O 密集型CPU 密集型任務。

ThreadPoolExecutorI/O 密集型(API 請求、爬蟲、檔案讀寫)
ProcessPoolExecutorCPU 密集型(數據計算、影像處理)
ThreadPoolExecutor多執行緒(共享記憶體)
ProcessPoolExecutor多進程(獨立記憶體)
ThreadPoolExecutor是(Python GIL 只允許單執行緒運行 Python 指令)
ProcessPoolExecutor否(每個進程有獨立 Python 解釋器)
ThreadPoolExecutor低(執行緒切換快)
ProcessPoolExecutor高(需要額外的記憶體空間)

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 程式的執行效率!