Python 多進程完整指南|multiprocessing 的用法、最佳化與錯誤對策

目次

1. 基礎篇:什麼是 Python 的多進程?

1.1 什麼是多進程?

多進程是一種 同時執行多個進程(獨立的執行單位)的技術。在 Python 中,可以透過 multiprocessing 模組輕鬆實作多進程。

多進程的特點

  • 每個進程擁有獨立的記憶體空間
  • 可以最大化利用 CPU 核心
  • 需要進程之間的通訊(使用 QueuePipe

具體的使用情境

  • 涉及大量計算的處理(機器學習、數值模擬)
  • 需要充分運用 CPU 的任務(影像處理、資料分析)

1.2 與多執行緒的差異

Python 也有「多執行緒」這種並行處理機制。那麼多進程與多執行緒有什麼不同呢?

項目多進程多執行緒
記憶體共享不共享(各自獨立)共享(同一進程內)
GIL 的影響不受影響受影響
適合 CPU 密集任務
適合 I/O 密集任務
資料傳遞方式需使用 QueuePipe可直接使用共享記憶體

什麼是 GIL(Global Interpreter Lock)?

Python 的標準直譯器(CPython)中有一個稱為 GIL 的機制,即使使用多執行緒,也無法同時執行多個執行緒。因此,若想充分利用 CPU,建議使用多進程。

1.3 使用 Python 實作簡單的多進程範例

import multiprocessing
import time

def worker(n):
    print(f"プロセス {n} 開始")
    time.sleep(2)
    print(f"プロセス {n} 終了")

if __name__ == "__main__":
    process_list = []

    # 3つのプロセスを作成
    for i in range(3):
        p = multiprocessing.Process(target=worker, args=(i,))
        process_list.append(p)
        p.start()

    # 全てのプロセスが終了するまで待機
    for p in process_list:
        p.join()

    print("全プロセス終了")

1.4 使用多進程時的注意事項

1. 在 Windows 系統上必須加上 if __name__ == "__main__":

在 Windows 環境中,使用 multiprocessing.Process() 時,若未加上 if __name__ == "__main__": 會發生錯誤

錯誤的範例(會出現錯誤)
import multiprocessing

def worker():
    print("Hello from process")

p = multiprocessing.Process(target=worker)
p.start()

此程式碼在 Windows 系統中會報錯

正確的範例
import multiprocessing

def worker():
    print("Hello from process")

if __name__ == "__main__":
    p = multiprocessing.Process(target=worker)
    p.start()
    p.join()

加上 if __name__ == "__main__": 即可在 Windows 上正確執行。

1.5 小結

  • 什麼是多進程?同時執行多個進程的方式
  • 與多執行緒的差異不受 GIL 限制,適合 CPU 密集型任務
  • 在 Python 中的簡單範例 → 使用 multiprocessing.Process()
  • Windows 注意事項 → 必須加上 if __name__ == "__main__":

2. 實戰篇:multiprocessing 模組的使用方式

2.1 multiprocessing 模組的概要

multiprocessing 模組是 Python 的標準函式庫,提供 基於進程的平行處理 功能。
透過這個模組,可以充分運用 CPU 核心,並避開 GIL 的限制。

multiprocessing 的主要功能

功能說明
Process建立並執行個別的進程
Queue用於進程之間的資料傳遞
Pipe在兩個進程間傳遞資料
Value & Array讓進程間使用共享記憶體
Pool建立進程池,有效率地執行平行處理

2.2 Process 類別的基本使用方法

要在 Python 中建立新的進程,可以使用 multiprocessing.Process 類別。

基本的進程建立範例

import multiprocessing
import time

def worker(n):
    print(f"プロセス {n} 開始")
    time.sleep(2)
    print(f"プロセス {n} 終了")

if __name__ == "__main__":
    p1 = multiprocessing.Process(target=worker, args=(1,))
    p2 = multiprocessing.Process(target=worker, args=(2,))

    p1.start()
    p2.start()

    p1.join()
    p2.join()

    print("全プロセス終了")

2.3 進程間通訊(Queue & Pipe)

使用 Queue 傳送與接收資料

import multiprocessing

def worker(q):
    q.put("Hello from child process")

if __name__ == "__main__":
    q = multiprocessing.Queue()
    p = multiprocessing.Process(target=worker, args=(q,))
    p.start()
    p.join()

    # 從子進程取得資料
    print(q.get())

2.4 使用 ValueArray 進行共享記憶體

import multiprocessing

def worker(val, arr):
    val.value = 3.14  # 修改共享記憶體中的值
    arr[0] = 42       # 修改陣列中的值

if __name__ == "__main__":
    val = multiprocessing.Value('d', 0.0)  # 'd' 表示 double 型別
    arr = multiprocessing.Array('i', [0, 1, 2])  # 'i' 表示整數型別

    p = multiprocessing.Process(target=worker, args=(val, arr))
    p.start()
    p.join()

    print(f"val: {val.value}, arr: {arr[:]}")

2.5 使用 Pool 類別進行進程管理

使用 Pool 進行平行處理

import multiprocessing

def square(n):
    return n * n

if __name__ == "__main__":
    with multiprocessing.Pool(4) as pool:
        results = pool.map(square, range(10))

    print(results)

2.6 小結

  • 使用 multiprocessing 模組可以 輕鬆實作平行處理
  • 使用 Process 類別建立個別進程
  • 使用 QueuePipe 可以 在進程之間傳遞資料
  • 透過 ValueArray 可以 實現共享記憶體
  • 使用 Pool 類別可以 高效處理大量資料

3. 進階篇:錯誤處理與效能最佳化

3.1 在 multiprocessing 中常見的錯誤與對策

錯誤 1:Windows 環境中未加上 if __name__ == "__main__": 的錯誤

錯誤訊息
RuntimeError: freeze_support() must be called if program is run in frozen mode
解決方法
import multiprocessing

def worker():
    print("Hello from process")

if __name__ == "__main__":  # 這一行是必要的
    p = multiprocessing.Process(target=worker)
    p.start()
    p.join()

錯誤 2:PicklingError(無法在進程間傳遞函式)

錯誤訊息
AttributeError: Can't pickle local object 'main.<locals>.<lambda>'
解決方法
import multiprocessing

def square(x):  # 使用全域函式
    return x * x

if __name__ == "__main__":
    with multiprocessing.Pool(4) as pool:
        results = pool.map(square, range(10))  # 避免使用 lambda
    print(results)

錯誤 3:死鎖(進程無限等待)

解決方法
import multiprocessing

def worker(q):
    q.put("data")

if __name__ == "__main__":
    q = multiprocessing.Queue()
    p = multiprocessing.Process(target=worker, args=(q,))
    p.start()
    print(q.get())  # 先取得資料
    p.join()        # 再等待進程結束

3.2 效能最佳化技巧

最佳化 1:適當設定進程數量

import multiprocessing

def worker(n):
    return n * n

if __name__ == "__main__":
    num_workers = multiprocessing.cpu_count()  # 取得 CPU 核心數量
    with multiprocessing.Pool(num_workers) as pool:
        results = pool.map(worker, range(100))
    print(results)

最佳化 2:使用 Pool.starmap()

import multiprocessing

def multiply(a, b):
    return a * b

if __name__ == "__main__":
    with multiprocessing.Pool(4) as pool:
        results = pool.starmap(multiply, [(1, 2), (3, 4), (5, 6)])
    print(results)

最佳化 3:活用共享記憶體

import multiprocessing
import ctypes

def worker(shared_array):
    shared_array[0] = 99  # 修改共享記憶體的值

if __name__ == "__main__":
    shared_array = multiprocessing.Array(ctypes.c_int, [1, 2, 3])  # 建立共享記憶體
    p = multiprocessing.Process(target=worker, args=(shared_array,))
    p.start()
    p.join()
    print(shared_array[:])  # [99, 2, 3]

3.3 小結

  • 說明了 multiprocessing 中常見的 錯誤與解決方法
  • 效能最佳化的重點:
  • 正確設定進程數量
  • 活用 starmap()
  • 使用共享記憶體來加速處理

4. 常見問題:FAQ 與對策

4.1 應該使用 Python 的多進程還是多執行緒?

解答

  • CPU 密集型(需要大量計算的處理)建議使用多進程(multiprocessing)
  • I/O 密集型(檔案、網路等處理)建議使用多執行緒(threading)
處理類型適合的平行處理方式
CPU 密集型(數值運算、影像處理等)多進程(multiprocessing)
I/O 密集型(檔案操作、API 請求等)多執行緒(threading)

4.2 為什麼覺得 multiprocessing 很「慢」?

解答

  • 建立進程的成本較高 → 使用 Pool 來重複利用進程
  • 資料複製太多 → 使用共享記憶體(Value, Array)來改善
  • 處理大量的小任務 → 可以考慮改用 concurrent.futures.ThreadPoolExecutor
import multiprocessing

def worker(n):
    return n * n

if __name__ == "__main__":
    with multiprocessing.Pool(multiprocessing.cpu_count()) as pool:
        results = pool.map(worker, range(100))
    print(results)

4.3 如何在多進程中共享字典或列表?

解答

可以使用 multiprocessing.Manager() 來實現共享物件

import multiprocessing

def worker(shared_list):
    shared_list.append(100)  # 更新共享列表

if __name__ == "__main__":
    with multiprocessing.Manager() as manager:
        shared_list = manager.list([1, 2, 3])
        p = multiprocessing.Process(target=worker, args=(shared_list,))
        p.start()
        p.join()
        print(shared_list)  # [1, 2, 3, 100]

4.4 使用 multiprocessing.Pool 時常見的錯誤與對策是什麼?

錯誤訊息原因對策
AttributeError: Can't pickle local object傳遞了 lambda 或區域函式改用全域函式
RuntimeError: freeze_support() must be called在 Windows 中漏寫 if __name__ == "__main__":加入 if __name__ == "__main__":
EOFError: Ran out of inputPool 中的某個進程異常中斷確保正確呼叫 pool.close()pool.join()

4.5 要如何偵錯 Python 的多進程?

解答

可以使用 multiprocessing.log_to_stderr() 來輸出進程的日誌

import multiprocessing
import logging

def worker(n):
    logger = multiprocessing.get_logger()
    logger.info(f"プロセス {n} 実行中")

if __name__ == "__main__":
    multiprocessing.log_to_stderr(logging.INFO)  # 啟用日誌
    p = multiprocessing.Process(target=worker, args=(1,))
    p.start()
    p.join()

5. 總結與延伸學習資源

5.1 文章總結

多進程的基礎知識

  • 什麼是多進程?同時執行多個進程,最大化 CPU 利用率的技術
  • 與多執行緒的差異
  • 多進程 → 適合 CPU 密集型處理(例如數值運算、影像處理)
  • 多執行緒 → 適合 I/O 密集型處理(例如檔案處理、網路通訊)

multiprocessing 模組的使用方法

  • 使用 Process 類別來 建立個別的進程
  • 透過 QueuePipe進行進程間資料傳輸
  • 使用 ValueArray共享記憶體
  • 利用 Pool 類別來 有效率地執行平行處理

錯誤處理與效能最佳化

  • 常見錯誤處理方法
  • 如果未加上 if __name__ == "__main__":在 Windows 中會出現錯誤
  • 使用 lambda 或區域函式會導致 PicklingError
  • 忘記使用 Queue.get() 可能會 導致死鎖
  • 效能最佳化技巧
  • 使用 Pool 降低重複建立進程的成本
  • 使用 starmap() 傳遞多個參數
  • 使用 multiprocessing.shared_memory 減少資料複製開銷

5.2 延伸學習資源

1. Python 官方文件

2. 線上教學文章

5.3 展望未來應用

妥善運用 Python 的 multiprocessing 模組,能夠有效提升 CPU 使用效率,打造高效能的程式

接下來可以學習的技術

  • 非同步處理(asyncio) → 用於 I/O 密集型的平行處理
  • concurrent.futures → 統一管理 ThreadPoolExecutor 與 ProcessPoolExecutor

5.4 結語

本篇文章從 基礎到實作、再到進階應用,詳細介紹了 Python 多進程 的完整知識。

希望你能將所學應用在實際專案中,發揮更高效能的開發實力!🚀