Python 的多執行緒受限於 GIL,無法有效利用多核心 CPU。實際應用中,即使多個執行緒同時運作,CPU 使用率也難以提升。相對地,多工處理能繞過 GIL 限制,透過建立多個獨立程式,充分發揮多核心效能。本文提供的程式碼範例展示瞭如何使用 multiprocessing 模組建立多個程式,執行相同的計算任務,並觀察 CPU 使用率的顯著提升。此外,也介紹了 concurrent.futures 模組,提供更簡潔的平行計算方式,適合 I/O 密集型任務。兩種方法各有優劣,開發者需根據實際應用場景選擇合適的方案。
多工處理與多執行緒:Python 中的 CPU 使用率比較
在 Python 中,多工處理和多執行緒是兩種不同的方法,用於利用多核心 CPU 的能力。然而,由於 CPython 的全域解譯鎖(GIL),多執行緒的效能受到限制。在本文中,我們將比較使用多執行緒和多工處理的差異,並探討如何使用 multiprocessing 模組來提高 CPU 使用率。
多執行緒的限制
使用多執行緒可以將工作負載分配給多個核心,但是由於 GIL 的限制,多執行緒無法完全發揮多核心 CPU 的能力。如前所述,使用多執行緒的程式只能使用到 116% 的 CPU 能力,即使有八個執行緒在平行執行。
使用多工處理
另一方面,使用多工處理可以建立新的程式,這些程式可以獨立執行,不受 GIL 的限制。Python 的 multiprocessing 模組提供了一個高層次的介面,用於建立新的程式。透過使用 multiprocessing 模組,我們可以重寫前面的例子,使用多工處理來提高 CPU 使用率。
示例程式碼
以下是使用 multiprocessing 模組的示例程式碼:
import random
import multiprocessing
def compute(results):
results.append(sum([random.randint(1, 100) for i in range(1000000)]))
with multiprocessing.Manager() as manager:
results = manager.list()
workers = [multiprocessing.Process(target=compute, args=(results,)) for x in range(8)]
for worker in workers:
worker.start()
for worker in workers:
worker.join()
print("Results: %s" % results)
結果比較
使用多工處理的程式可以消耗到 332% 的 CPU 能力,比多執行緒的程式快了三倍。這是因為多工處理可以建立新的程式,這些程式可以獨立執行,不受 GIL 的限制。
使用多程式和並發期物實作平行計算
在進行多程式計算時,Python 的 multiprocessing 模組提供了一種方便的方式來實作平行計算。下面是一個使用 multiprocessing 的例子:
import multiprocessing
import random
def compute(n):
return sum(random.randint(1, 100) for i in range(1000000))
if __name__ == '__main__':
# 啟動 8 個工作者
pool = multiprocessing.Pool(processes=8)
print("結果:%s" % pool.map(compute, range(8)))
在這個例子中,我們定義了一個 compute 函式,該函式計算了一個隨機數字的總和。然後,我們啟動了 8 個工作者,並使用 pool.map 函式將 compute 函式應用於範圍為 8 的數字列表上。
使用 multiprocessing 的優點是,它可以自動管理工作者程式的建立和終止,減少了手動管理程式的複雜性。
使用並發期物實作平行計算
Python 3.2 引入了 concurrent.futures 模組,該模組提供了一種簡單的方式來實作平行計算。下面是一個使用 concurrent.futures 的例子:
from concurrent import futures
import random
def compute():
return sum(random.randint(1, 100) for i in range(1000000))
with futures.ThreadPoolExecutor(max_workers=8) as executor:
futs = [executor.submit(compute) for _ in range(8)]
results = [f.result() for f in futs]
print("結果:%s" % results)
在這個例子中,我們定義了一個 compute 函式,該函式計算了一個隨機數字的總和。然後,我們建立了一個 ThreadPoolExecutor 物件,並提交 8 個任務到執行緒池中。最後,我們收集了每個任務的結果,並列印預出來。
使用 concurrent.futures 的優點是,它提供了一種簡單的方式來實作平行計算,同時也提供了對執行緒池的控制,減少了手動管理執行緒的複雜性。
比較兩種方法
兩種方法都可以實作平行計算,但是 concurrent.futures 的優點是,它提供了一種簡單的方式來實作平行計算,同時也提供了對執行緒池的控制。另一方面,multiprocessing 的優點是,它可以自動管理工作者程式的建立和終止,減少了手動管理程式的複雜性。
圖表翻譯:
flowchart TD
A[開始] --> B[選擇方法]
B --> C[multiprocessing]
B --> D[concurrent.futures]
C --> E[啟動工作者]
D --> F[提交任務]
E --> G[收集結果]
F --> G
G --> H[列印結果]
內容解密:
在這個流程圖中,我們首先選擇實作平行計算的方法,可以是 multiprocessing 或 concurrent.futures。如果選擇 multiprocessing,我們啟動工作者程式並提交任務。如果選擇 concurrent.futures,我們提交任務到執行緒池中。最後,我們收集每個任務的結果並列印預出來。
從效能最佳化視角來看,Python 的多工處理和多執行緒在 CPU 使用率方面表現出明顯差異。深入剖析 GIL 的影響後,可以發現多執行緒在 CPU 密集型任務中受限於單一核心執行,即使模擬多執行緒也僅能達到有限的 CPU 使用率提升。對比之下,多工處理藉由建立多個獨立程式,有效繞過 GIL 的限制,實作真正的平行計算,從而大幅提高 CPU 使用率,充分利用多核心架構的優勢。實務上,對於 CPU 密集型任務,技術團隊應優先考慮 multiprocessing 模組,並搭配適當的程式間通訊機制,例如 Manager 或 Queue,以最佳化資料交換和同步。然而,多工處理也引入了程式管理的額外開銷,因此並非所有情況都適用。對於 I/O 密集型任務,多執行緒或非同步程式設計模型可能更為有效。展望未來,隨著 Python 生態的持續發展,預計會有更多針對 GIL 的最佳化方案出現,例如更細粒度的鎖機制或替代的執行模型,進一步提升 Python 在多核心環境下的效能。玄貓認為,開發者應根據具體應用場景和效能需求,審慎選擇多工處理或多執行緒,並持續關注 Python 的發展趨勢,以做出最佳的技術決策。