01-5. Process vs Thread 完整對比

⏱️ 閱讀時間: 10 分鐘 🎯 難度: ⭐⭐ (簡單)


🎯 本篇重點

完整對比 Process 和 Thread 的差異,理解各自的優缺點和適用場景。


🤔 一句話解釋

Process: 擁有獨立記憶體空間的執行單位 Thread: 共享記憶體空間的輕量級執行單位


🏢 用公司來比喻

Process = 獨立的公司

公司 A(Process A)
├─ 辦公室(獨立記憶體空間)
├─ 資產(獨立資源)
├─ 員工(Thread)
└─ 財務(獨立帳戶)

公司 B(Process B)
├─ 辦公室(獨立記憶體空間)
├─ 資產(獨立資源)
├─ 員工(Thread)
└─ 財務(獨立帳戶)

公司 A 和 B 完全獨立,互不干擾

Thread = 公司內的員工

公司 A(Process)
├─ 辦公室(共享記憶體空間)
│  ├─ 員工 1(Thread 1)
│  ├─ 員工 2(Thread 2)
│  └─ 員工 3(Thread 3)
│
├─ 共用資源
│  ├─ 會議室
│  ├─ 影印機
│  └─ 茶水間
│
└─ 所有員工共享這些資源!

📊 核心差異對比

記憶體空間

特性ProcessThread
記憶體空間獨立共享
Code Segment各自獨立共享同一份
Data Segment各自獨立共享
Stack各自獨立各自獨立
Heap各自獨立共享
# Process:獨立記憶體
from multiprocessing import Process

global_var = 0  # 每個 Process 有自己的 global_var

def process_task():
    global global_var
    global_var += 1
    print(f"Process: global_var = {global_var}")

p1 = Process(target=process_task)
p2 = Process(target=process_task)
p1.start(); p2.start()
p1.join(); p2.join()

# 輸出:
# Process: global_var = 1
# Process: global_var = 1  ← 各自獨立!
# Thread:共享記憶體
from threading import Thread

global_var = 0  # 所有 Thread 共享同一個 global_var

def thread_task():
    global global_var
    global_var += 1
    print(f"Thread: global_var = {global_var}")

t1 = Thread(target=thread_task)
t2 = Thread(target=thread_task)
t1.start(); t2.start()
t1.join(); t2.join()

# 輸出:
# Thread: global_var = 1
# Thread: global_var = 2  ← 共享記憶體!

創建成本

操作ProcessThread
創建時間~1-5 ms~10-100 μs
記憶體消耗~10-100 MB~1-10 MB
Context Switch慢(~61 μs)快(~1 μs)
import time
from multiprocessing import Process
from threading import Thread

# 測試創建 1000 個 Process
start = time.time()
processes = [Process(target=lambda: None) for _ in range(1000)]
for p in processes:
    p.start()
for p in processes:
    p.join()
print(f"Process: {time.time() - start:.2f} 秒")

# 測試創建 1000 個 Thread
start = time.time()
threads = [Thread(target=lambda: None) for _ in range(1000)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"Thread: {time.time() - start:.2f} 秒")

輸出範例:

Process: 5.23 秒
Thread: 0.12 秒  ← Thread 快 40 倍!

通訊方式

通訊方式ProcessThread
複雜度複雜(需要 IPC)簡單(直接存取)
速度
安全性高(隔離)低(需要同步)
# Process:需要使用 IPC(例如:Queue)
from multiprocessing import Process, Queue

def producer(queue):
    for i in range(5):
        queue.put(f"資料 {i}")

def consumer(queue):
    while not queue.empty():
        data = queue.get()
        print(f"收到: {data}")

queue = Queue()
p1 = Process(target=producer, args=(queue,))
p2 = Process(target=consumer, args=(queue,))
p1.start(); p2.start()
p1.join(); p2.join()
# Thread:直接共享變數
from threading import Thread

shared_data = []

def producer():
    for i in range(5):
        shared_data.append(f"資料 {i}")

def consumer():
    while len(shared_data) > 0:
        data = shared_data.pop(0)
        print(f"收到: {data}")

t1 = Thread(target=producer)
t2 = Thread(target=consumer)
t1.start(); t2.start()
t1.join(); t2.join()

穩定性與隔離

特性ProcessThread
錯誤隔離✅ 一個崩潰不影響其他❌ 一個崩潰全部崩潰
記憶體洩漏✅ 隔離❌ 影響整個 Process
安全性✅ 高⚠️ 需要同步機制
# Process:錯誤隔離
from multiprocessing import Process

def task_crash():
    raise Exception("Process 崩潰!")

def task_ok():
    print("我還活著!")

p1 = Process(target=task_crash)
p2 = Process(target=task_ok)
p1.start(); p2.start()
p1.join(); p2.join()

# 輸出:
# 我還活著!  ← p2 不受 p1 崩潰影響
# Thread:一個崩潰全部崩潰
from threading import Thread

def task_crash():
    raise Exception("Thread 崩潰!")

def task_ok():
    print("我還活著!")

t1 = Thread(target=task_crash)
t2 = Thread(target=task_ok)
t1.start(); t2.start()

# 整個程式崩潰!

🎯 優缺點總結

Process 的優缺點

優點:

  • 獨立記憶體:一個 Process 崩潰不影響其他
  • 真正並行:多核 CPU 可同時執行(不受 Python GIL 限制)
  • 安全:Process 間完全隔離
  • 穩定:錯誤不會擴散

缺點:

  • 創建成本高:記憶體、時間消耗大
  • 通訊複雜:需要使用 IPC(Queue, Pipe, Socket)
  • Context Switch 慢:切換成本高
  • 記憶體消耗大:每個 Process 獨立空間

Thread 的優缺點

優點:

  • 創建成本低:快速、記憶體消耗小
  • 通訊簡單:直接共享變數
  • Context Switch 快:切換成本低
  • 資源共享:共享 Heap、Data Segment

缺點:

  • GIL 限制(Python):無法真正並行(CPU 密集型)
  • 錯誤擴散:一個 Thread 崩潰,整個 Process 崩潰
  • 同步複雜:需要 Lock、Semaphore 避免 Race Condition
  • 除錯困難:多 Thread 問題難以重現

🌳 決策樹:何時用 Process?何時用 Thread?

開始
  │
  ├─ 任務類型?
  │
  ├─ CPU 密集型(大量計算)?
  │  └─ YES → 用 Process ✅
  │           • 繞過 Python GIL
  │           • 真正並行計算
  │           • 範例:數據分析、機器學習
  │
  ├─ I/O 密集型(大量等待)?
  │  │
  │  ├─ 需要共享大量資料?
  │  │  └─ YES → 用 Thread ✅
  │  │           • 共享記憶體
  │  │           • 通訊簡單
  │  │           • 範例:Web 爬蟲、API 呼叫
  │  │
  │  └─ 需要高穩定性?
  │     └─ YES → 用 Process ✅
  │              • 錯誤隔離
  │              • 更穩定
  │
  └─ 混合型?
     └─ 根據瓶頸選擇

🔍 實際案例對比

案例 1:CPU 密集型(數據分析)

import time
from multiprocessing import Process
from threading import Thread

def cpu_task():
    """CPU 密集計算"""
    for i in range(10000000):
        _ = i * i

# 使用 Process(真正並行)
start = time.time()
processes = [Process(target=cpu_task) for _ in range(4)]
for p in processes:
    p.start()
for p in processes:
    p.join()
print(f"Process: {time.time() - start:.2f} 秒")

# 使用 Thread(受 GIL 限制)
start = time.time()
threads = [Thread(target=cpu_task) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"Thread: {time.time() - start:.2f} 秒")

輸出(4 核 CPU):

Process: 2.5 秒   ← 4 核並行,快!
Thread: 9.8 秒    ← GIL 限制,慢!

結論:CPU 密集型用 Process


案例 2:I/O 密集型(網路請求)

import time
import requests
from multiprocessing import Process
from threading import Thread

def io_task():
    """I/O 密集任務"""
    response = requests.get('https://httpbin.org/delay/1')

# 使用 Process
start = time.time()
processes = [Process(target=io_task) for _ in range(10)]
for p in processes:
    p.start()
for p in processes:
    p.join()
print(f"Process: {time.time() - start:.2f} 秒")

# 使用 Thread
start = time.time()
threads = [Thread(target=io_task) for _ in range(10)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"Thread: {time.time() - start:.2f} 秒")

輸出:

Process: 1.5 秒   ← 創建成本高
Thread: 1.1 秒    ← 創建快,效果相同

結論:I/O 密集型用 Thread(創建成本低)


📋 快速選擇表

場景推薦理由
數據分析ProcessCPU 密集,需要真正並行
機器學習訓練ProcessCPU 密集,繞過 GIL
Web 爬蟲ThreadI/O 密集,共享資料方便
API 呼叫ThreadI/O 密集,創建成本低
檔案處理ThreadI/O 密集
影像處理ProcessCPU 密集
資料庫查詢ThreadI/O 密集
Web ServerProcess穩定性、隔離性
長時間運行服務Process穩定性
短期批次任務Thread創建成本低

🔧 Python 實戰建議

最佳實踐組合

# 組合使用:Process Pool + Thread Pool
from multiprocessing import Pool
from concurrent.futures import ThreadPoolExecutor

def cpu_task(data):
    """CPU 密集"""
    return complex_calculation(data)

def io_task(url):
    """I/O 密集"""
    return requests.get(url)

# CPU 密集:使用 Process Pool
with Pool(processes=4) as pool:
    results = pool.map(cpu_task, data_list)

# I/O 密集:使用 Thread Pool
with ThreadPoolExecutor(max_workers=20) as executor:
    results = executor.map(io_task, url_list)

✅ 重點回顧

核心差異:

  • Process:獨立記憶體,真正並行,成本高
  • Thread:共享記憶體,GIL 限制,成本低

選擇原則:

  • CPU 密集 → Process
  • I/O 密集 → Thread(或 asyncio)
  • 需要穩定性 → Process
  • 需要快速創建 → Thread

關鍵理解:

  • ✅ Process 適合計算密集、需要隔離的場景
  • ✅ Thread 適合 I/O 密集、需要共享資料的場景
  • ✅ Python GIL 限制 Thread 的 CPU 並行能力
  • ✅ 實際應用中常常組合使用

上一篇: 01-4. Context Switch 詳解 下一篇: 02-1. Thread 是什麼


最後更新:2025-01-04

0%