目錄
02-6. Multi-threading vs Multi-processing 完整對比
⏱️ 閱讀時間: 15 分鐘 🎯 難度: ⭐⭐ (簡單)
🎯 本篇重點
深入理解 Multi-threading(多執行緒)和 Multi-processing(多進程)的核心差異、適用場景及實戰案例。
🤔 核心差異:一句話總結
Multi-threading:一個程式內創建多個 Thread,共享記憶體空間。 Multi-processing:一個程式創建多個獨立 Process,各自擁有完全隔離的記憶體空間。
🏢 公司組織比喻
Multi-threading = 開放式辦公室
Yoru-Karu Studio(單一公司 = 1 個 Process)
│
├─ 共享辦公空間(共享記憶體)
│ ├─ 員工 A(Thread 1)→ 處理訂單
│ ├─ 員工 B(Thread 2)→ 接聽客服電話
│ ├─ 員工 C(Thread 3)→ 更新官網內容
│ └─ 員工 D(Thread 4)→ 回覆客戶郵件
│
└─ 共享資源
├─ 共用白板、檔案櫃(所有員工可直接存取)
├─ 溝通超快(面對面對話)
├─ 但可能搶資源(需要排隊協調)
└─ 一人出錯可能影響全辦公室特點:
- ✅ 溝通極快(直接存取共享變數)
- ✅ 資源共享(記憶體、檔案描述符)
- ✅ 創建成本低
- ⚠️ 需要同步機制(Lock、Semaphore)
- ⚠️ 一個 Thread 崩潰可能影響整個 Process
Multi-processing = 獨立分公司
Yoru-Karu Group(母公司)
│
├─ 台北分公司(Process 1, PID: 1001)
│ ├─ 獨立辦公室(獨立記憶體空間)
│ ├─ 獨立資源(自己的檔案、資料庫連線)
│ └─ 員工 A, B, C
│
├─ 高雄分公司(Process 2, PID: 1002)
│ ├─ 獨立辦公室(獨立記憶體空間)
│ ├─ 獨立資源
│ └─ 員工 D, E, F
│
├─ 台中分公司(Process 3, PID: 1003)
│ └─ ...
│
└─ 溝通方式
├─ 電話、郵件、視訊會議(IPC: Pipe, Queue, Socket)
├─ 各自獨立運作
├─ 一間分公司倒閉不影響其他分公司
└─ 溝通成本較高特點:
- ✅ 完全隔離(穩定性高)
- ✅ 可利用多核 CPU 真正並行
- ✅ 無 GIL 限制(Python)
- ⚠️ 溝通成本高(需要 IPC)
- ⚠️ 記憶體佔用大
💻 架構對比圖
Multi-threading 記憶體架構
Process (PID: 1000)
├─ 記憶體佈局
│ ├─ Code Segment (程式碼) ← 所有 Thread 共享
│ ├─ Data Segment (全域變數) ← 所有 Thread 共享
│ ├─ Heap (動態分配) ← 所有 Thread 共享
│ └─ Stack
│ ├─ Thread 1 Stack (獨立) ← 區域變數、函式呼叫
│ ├─ Thread 2 Stack (獨立)
│ ├─ Thread 3 Stack (獨立)
│ └─ Thread 4 Stack (獨立)
│
├─ 共享資源
│ ├─ 檔案描述符
│ ├─ 全域變數
│ └─ Heap 物件
│
└─ Thread
├─ Thread 1 (主 Thread)
├─ Thread 2
├─ Thread 3
└─ Thread 4特點:
- 所有 Thread 可直接讀寫 Heap、Data Segment
- 快速但需要同步
Multi-processing 記憶體架構
Master Process (PID: 1000)
└─ 創建並管理子 Process
Worker Process 1 (PID: 1001)
├─ 完全獨立的記憶體空間
│ ├─ Code Segment (獨立副本)
│ ├─ Data Segment (獨立副本)
│ ├─ Heap (獨立副本)
│ └─ Stack
│
└─ 獨立資源
├─ 獨立的檔案描述符
├─ 獨立的全域變數
└─ 無法直接存取其他 Process
Worker Process 2 (PID: 1002)
├─ 完全獨立的記憶體空間
│ └─ ... (完全隔離)
Worker Process 3 (PID: 1003)
└─ ...特點:
- 每個 Process 有獨立記憶體
- 需要 IPC 才能溝通
🔍 實戰案例對比
案例 1:創建與啟動
Multi-threading
from threading import Thread
import os
import time
def worker(name):
print(f"[{name}] PID: {os.getpid()}, 開始工作")
time.sleep(1)
print(f"[{name}] 完成")
# 創建 3 個 Thread
threads = []
start = time.time()
for i in range(3):
t = Thread(target=worker, args=(f'Thread-{i}',))
t.start()
threads.append(t)
for t in threads:
t.join()
print(f"總耗時: {time.time() - start:.3f} 秒")輸出:
[Thread-0] PID: 1000, 開始工作 ← 同一個 PID
[Thread-1] PID: 1000, 開始工作
[Thread-2] PID: 1000, 開始工作
[Thread-0] 完成
[Thread-1] 完成
[Thread-2] 完成
總耗時: 1.002 秒 ← 創建成本低Multi-processing
from multiprocessing import Process
import os
import time
def worker(name):
print(f"[{name}] PID: {os.getpid()}, 開始工作")
time.sleep(1)
print(f"[{name}] 完成")
# 創建 3 個 Process
if __name__ == '__main__':
processes = []
start = time.time()
for i in range(3):
p = Process(target=worker, args=(f'Process-{i}',))
p.start()
processes.append(p)
for p in processes:
p.join()
print(f"總耗時: {time.time() - start:.3f} 秒")輸出:
[Process-0] PID: 1001, 開始工作 ← 不同的 PID
[Process-1] PID: 1002, 開始工作
[Process-2] PID: 1003, 開始工作
[Process-0] 完成
[Process-1] 完成
[Process-2] 完成
總耗時: 1.05 秒 ← 創建成本稍高案例 2:記憶體共享 vs 隔離
Multi-threading(共享記憶體)
from threading import Thread, Lock
# 共享變數(所有 Thread 可見)
shared_counter = {'value': 0}
lock = Lock()
def increment():
for _ in range(100000):
with lock: # 需要 Lock 保護
shared_counter['value'] += 1
# 兩個 Thread 同時修改共享變數
t1 = Thread(target=increment)
t2 = Thread(target=increment)
t1.start(); t2.start()
t1.join(); t2.join()
print(f"Counter: {shared_counter['value']}")
# 輸出:Counter: 200000 ← 正確(有 Lock)沒有 Lock 的結果:
# 如果不用 Lock
shared_counter['value'] += 1 # 沒有 Lock
# 可能輸出:Counter: 150000 ← Race Condition!Multi-processing(記憶體隔離)
from multiprocessing import Process
# 每個 Process 會得到獨立的變數副本
shared_counter = {'value': 0}
def increment():
for _ in range(100000):
shared_counter['value'] += 1
print(f"Process {os.getpid()} counter: {shared_counter['value']}")
# 兩個 Process 各自修改自己的副本
p1 = Process(target=increment)
p2 = Process(target=increment)
p1.start(); p2.start()
p1.join(); p2.join()
print(f"Main Process counter: {shared_counter['value']}")輸出:
Process 1001 counter: 100000 ← Process 1 的獨立副本
Process 1002 counter: 100000 ← Process 2 的獨立副本
Main Process counter: 0 ← 主 Process 的原始值(未被修改)如果真的需要共享:
from multiprocessing import Process, Value, Lock
def increment(counter, lock):
for _ in range(100000):
with lock:
counter.value += 1
if __name__ == '__main__':
# 使用共享記憶體
counter = Value('i', 0) # 共享整數
lock = Lock()
p1 = Process(target=increment, args=(counter, lock))
p2 = Process(target=increment, args=(counter, lock))
p1.start(); p2.start()
p1.join(); p2.join()
print(f"Counter: {counter.value}")
# 輸出:Counter: 200000案例 3:CPU 密集型任務(關鍵差異!)
import time
def cpu_intensive_task():
"""CPU 密集運算"""
total = 0
for i in range(10000000):
total += i * i
return total
# 測試單執行
start = time.time()
cpu_intensive_task()
cpu_intensive_task()
cpu_intensive_task()
cpu_intensive_task()
print(f"單執行: {time.time() - start:.2f} 秒")單執行輸出:
單執行: 10.0 秒Multi-threading(受 GIL 限制)
from threading import Thread
import time
start = time.time()
threads = [Thread(target=cpu_intensive_task) for _ in range(4)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Multi-threading: {time.time() - start:.2f} 秒")輸出(4 核 CPU):
Multi-threading: 10.5 秒 ← 沒有加速!甚至更慢(GIL + Context Switch)原因:
- Python GIL(Global Interpreter Lock)限制
- 同一時間只有一個 Thread 可以執行 Python 程式碼
- 多個 Thread 輪流持有 GIL,無法真正並行
Multi-processing(真正並行)
from multiprocessing import Process, Pool
import time
# 方法 1:手動創建 Process
start = time.time()
processes = [Process(target=cpu_intensive_task) for _ in range(4)]
for p in processes:
p.start()
for p in processes:
p.join()
print(f"Multi-processing: {time.time() - start:.2f} 秒")
# 方法 2:使用 Process Pool(推薦)
start = time.time()
with Pool(processes=4) as pool:
pool.map(lambda x: cpu_intensive_task(), range(4))
print(f"Process Pool: {time.time() - start:.2f} 秒")輸出(4 核 CPU):
Multi-processing: 2.6 秒 ← 接近 4 倍加速!
Process Pool: 2.5 秒 ← Pool 更高效原因:
- 每個 Process 有獨立的 Python 解釋器
- 每個 Process 有自己的 GIL
- 4 個 Process 可在 4 個 CPU 核心上真正並行執行
案例 4:I/O 密集型任務
import time
import requests
def io_intensive_task(url):
"""I/O 密集任務:網路請求"""
response = requests.get(url)
return response.status_code
urls = ['https://httpbin.org/delay/1'] * 10Multi-threading(推薦)
from threading import Thread
start = time.time()
threads = [Thread(target=io_intensive_task, args=(url,)) for url in urls]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Multi-threading: {time.time() - start:.2f} 秒")輸出:
Multi-threading: 1.2 秒 ← 快速!(10 個請求並發)原因:
- I/O 操作時會自動釋放 GIL
- 其他 Thread 可以在 I/O 等待期間執行
- 創建成本低
Multi-processing(不推薦)
from multiprocessing import Pool
start = time.time()
with Pool(processes=10) as pool:
pool.map(io_intensive_task, urls)
print(f"Multi-processing: {time.time() - start:.2f} 秒")輸出:
Multi-processing: 1.5 秒 ← 也快,但成本高結論:
- I/O 密集型用 Thread 即可
- Process 創建成本高,沒必要
📊 完整對比表
基本特性對比
| 特性 | Multi-threading | Multi-processing |
|---|---|---|
| 記憶體模型 | 共享記憶體 | 獨立記憶體 |
| PID | 相同 | 不同 |
| 創建速度 | 快(微秒級) | 慢(毫秒級) |
| 記憶體佔用 | 小(幾 KB/Thread) | 大(幾 MB/Process) |
| 溝通方式 | 直接存取共享變數 | IPC(Pipe, Queue, Socket) |
| 溝通速度 | 極快 | 慢 |
| 穩定性 | 低(一個崩潰全崩潰) | 高(隔離) |
Python 特定特性
| 特性 | Multi-threading | Multi-processing |
|---|---|---|
| GIL 影響 | ✅ 受限制 | ❌ 無影響 |
| CPU 密集型 | ❌ 無加速 | ✅ 線性加速 |
| I/O 密集型 | ✅ 有加速 | ✅ 有加速(成本高) |
| 標準庫模組 | threading | multiprocessing |
| 最大數量 | 幾千個(視系統) | 幾十個(CPU 核心數) |
同步與安全
| 特性 | Multi-threading | Multi-processing |
|---|---|---|
| Race Condition | ✅ 容易發生 | ❌ 不會發生(獨立記憶體) |
| 需要 Lock | ✅ 是 | ❌ 否(除非用共享記憶體) |
| Dead Lock 風險 | ✅ 高 | ❌ 低 |
| 除錯難度 | 高 | 中 |
🎯 選擇指南
選擇 Multi-threading 的場景
# ✅ I/O 密集型:網路、檔案、資料庫
from threading import Thread
import requests
def download_file(url):
response = requests.get(url) # I/O 操作時自動釋放 GIL
return response.content
# 適合用 Thread
threads = [Thread(target=download_file, args=(url,)) for url in urls]適用場景:
- 🌐 網路爬蟲:大量 HTTP 請求
- 📁 檔案 I/O:讀寫大量小檔案
- 🗄️ 資料庫查詢:等待 SQL 查詢結果
- 🔌 API 呼叫:呼叫第三方 API
- 💬 聊天伺服器:等待訊息
- 📧 郵件發送:SMTP I/O 等待
優點:
- 創建快
- 記憶體少
- 溝通簡單
選擇 Multi-processing 的場景
# ✅ CPU 密集型:計算、資料處理
from multiprocessing import Pool
def process_data(data):
# CPU 密集運算
result = complex_computation(data)
return result
# 適合用 Process
with Pool(processes=8) as pool:
results = pool.map(process_data, large_dataset)適用場景:
- 🧮 科學計算:矩陣運算、模擬
- 🖼️ 影像處理:濾鏡、轉檔
- 🎬 影片處理:編碼、轉檔
- 📊 大數據分析:資料清洗、統計
- 🤖 機器學習:模型訓練
- 🔐 密碼學:加密、解密
- 🎮 遊戲物理引擎:碰撞檢測
優點:
- 無 GIL 限制
- 真正並行
- 穩定性高
混合使用(Process + Thread)
from multiprocessing import Process
from threading import Thread
import requests
def worker_process(url_chunk):
"""每個 Process 內用多個 Thread"""
def download(url):
return requests.get(url).content
# Process 內的 Thread
threads = [Thread(target=download, args=(url,)) for url in url_chunk]
for t in threads:
t.start()
for t in threads:
t.join()
# 將 URL 分配給多個 Process
url_chunks = [urls[i:i+10] for i in range(0, len(urls), 10)]
processes = [Process(target=worker_process, args=(chunk,)) for chunk in url_chunks]
for p in processes:
p.start()
for p in processes:
p.join()案例:Gunicorn Web Server
Gunicorn Master Process
├─ Worker Process 1 (CPU core 1)
│ ├─ Thread 1 → 處理 HTTP 請求 A
│ ├─ Thread 2 → 處理 HTTP 請求 B
│ └─ Thread 3 → 處理 HTTP 請求 C
│
├─ Worker Process 2 (CPU core 2)
│ ├─ Thread 1 → 處理 HTTP 請求 D
│ └─ ...
│
└─ Worker Process 4 (CPU core 4)
└─ ...⚠️ 常見陷阱
陷阱 1:Thread 誤用於 CPU 密集型
# ❌ 錯誤:Thread 無法加速 CPU 運算
from threading import Thread
def cpu_task():
return sum(i*i for i in range(10000000))
threads = [Thread(target=cpu_task) for _ in range(4)]
# GIL 限制,無加速
# ✅ 正確:用 Process
from multiprocessing import Pool
with Pool(4) as pool:
results = pool.map(lambda x: sum(i*i for i in range(10000000)), range(4))陷阱 2:Process 忘記 if name == ‘main’
# ❌ 錯誤:Windows 上會無限創建 Process
from multiprocessing import Process
def worker():
print("Working")
p = Process(target=worker)
p.start()
# ✅ 正確:加上保護
if __name__ == '__main__':
p = Process(target=worker)
p.start()
p.join()陷阱 3:創建過多 Process
# ❌ 錯誤:創建太多 Process 導致系統崩潰
for i in range(1000):
Process(target=work).start()
# ✅ 正確:使用 Process Pool
from multiprocessing import Pool
import os
with Pool(processes=os.cpu_count()) as pool:
pool.map(work, range(1000))✅ 重點回顧
Multi-threading:
- ✅ 共享記憶體,溝通極快
- ✅ 創建成本低,適合大量 Thread
- ✅ 適合 I/O 密集型任務
- ❌ GIL 限制,無法加速 CPU 運算
- ❌ 需要 Lock 防止 Race Condition
Multi-processing:
- ✅ 獨立記憶體,穩定性高
- ✅ 無 GIL,可真正並行
- ✅ 適合 CPU 密集型任務
- ❌ 創建成本高
- ❌ 溝通需要 IPC,較慢
選擇原則:
- I/O 密集型 → Multi-threading
- CPU 密集型 → Multi-processing
- 混合型 → Process + Thread
- 大量並發 → Thread
- 需要隔離 → Process
上一篇: 02-5. Thread Pool 實戰 下一篇: 03-1. IPC 概述
最後更新:2025-01-06