前言
這一篇文是上一篇文的進階版,若還沒有看過上集可以看這篇:
【Python 平行運算 #2】multiprocessing – 01 | 用多核心來執行程式 (內含範例程式碼) sample code
這篇會更著重在說明「multiprocessing Pool」的用法,
差別在會使用 multiprocessing Pool 可取得回傳結果,並讓系統自動分配資源,
而使用 Process 不會回傳結果 (除非使用 mp.Queue 等等方式)
一樣的前言介紹
在 python 中有 thread 與 multiprocess 兩種平行處理程式的方式,
若只是單純的平行需求,我們可以使用 threading 這個模組來快速完成平行處理的方式。
但是 threading 只是透過頻繁的 CPU context-switch 的方式實現,
要真正實現多核心 CPU 的平行運算,我們需要使用 multiprocessing,
將任務指派給多個核心進行操作。
multiprocessing 在資料傳遞上,會因為需要將資料轉移至其他 CPU 上進行運算,
因此會需要考慮資料搬運的時間,
而多核心真正的實現「平行運算的功能」,當任務較為複雜時,效率一定比較好。
thread 與 multiprocess 比較
threading 重點摘要
threading 是透過 context-switch 的方式實現
也就是說,我們是透過 CPU 的不斷切換 (context-switch),實現平行的功能。
當大量使用 threading 執行平行的功能時,反而會因為大量的 context-switch,
「實現了程式平行的功能,但也因為大量的 context-switch ,使得程式執行速度更慢」。
multiprocessing 重點摘要
multiprocessing 在資料傳遞上,會因為需要將資料轉移至其他 CPU 上進行運算,
因此會需要考慮資料搬運的時間,
而多核心真正的實現「平行運算的功能」,當任務較為複雜時,效率一定比較好。
thread 與 multiprocess 比較圖
從下圖我們可以看到任務被完成的「概念」時間
- main 1~4, main-end
- 任務 A1, A2
- 任務 B1, B2
- 任務 C1, C2
請留意圖中粗線的部分:
* 在 multithread 中,
CPU context-switch 會額外消耗我們程式執行的時間,程式實際完成時間可能比一般的還要慢。
- 在 multiprocess 中,
我們需要將資料轉移至其他 CPU 會額外消耗我們程式執行的時間,如果任務過於簡單,效益可能不大。
雖然示意圖中明顯感覺較快,但前提是任務夠複雜
也就是說,「任務難度執行的時間 > 資料轉移至其他 CPU 的時間效益」,不然只會更慢。
multiprocess Pool 的使用 (with map)
這邊再提一次,這一篇文是上一篇文的進階版,若還沒有看過上集可以看這篇:
【Python 平行運算 #2】multiprocessing – 01 | 用多核心來執行程式 (內含範例程式碼) sample code
使用 multiprocessing Pool 可取得回傳結果,並讓系統自動分配資源,
而使用 Process 不會回傳結果 (除非使用 mp.Queue 等等方式)
而這邊我們執行 pool 內的任務的時候,使用「map」將任務一個個分配下去。
範例程式碼 (multiprocess Pool)
import multiprocessing as mp
def task(num):
return 'The pool return result: ' + str(num)
if __name__=='__main__':
pool = mp.Pool()
res = pool.map(task, range(10))
print(res)
運行結果
說明
- pool = mp.Pool()
此外,Pool 內可以使用參數 processes = 「想要的 CPU 核數量」,
例如:pool = mp.Pool(processes=4) ,就是我們指定了 4個 CPU 核。
(預設就是 CPU 的對應核心數量)
- res = pool.map(task, range(10))
以 map 的方式,將 list 中一個個的參數傳入,
例如此處 range(10) 其實就等於 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
傳入此 10 個任務,並得到對應的 10 個結果回傳。
- print(res)
印出結果,注意結果都會被「存在一個 list 裡面」,
如果想要單獨的結果,需要一個個拿出來。 (如上圖)
multiprocess Pool 的使用 (with apply_async)
map, apply_async 都是能讓 pool 開始執行任務的方法,
差別在於 map 可以傳入一個 list,
而 apply_async 只能傳入一個 變數。
範例程式碼 (multiprocess Pool)
import multiprocessing as mp
def task(num):
return 'The pool return result: ' + str(num)
if __name__=='__main__':
pool = mp.Pool()
res = pool.apply_async(task, (0,))
print(res)
print(res.get()) # use get() to get apply_async result
運行結果
說明
- pool = mp.Pool()
Pool 內可以使用參數 processes = 「想要的 CPU 核數量」,
例如:pool = mp.Pool(processes=4) ,就是我們指定了 4個 CPU 核。
(預設就是 CPU 的對應核心數量)
- res = pool.apply_async(task, (0,))
apply_async 的方式不同於 map,一次只能傳入一個變數
例如:此處只能傳進 0,而得到的結果是封裝好的 applyResult,
我們需要再使用 get() 來得到結果。
- print(res.get())
如同上述說明,res 是封裝好的 applyResult,
我們需要再使用 get() 來得到 apply_async 的分析結果。
multiprocess Pool 的使用 (使用 apply_async 達到 map 的效果)
我們說 apply_async 只能一次執行一個,
難道就不能讓他跑很多次,讓他有類似 map 的效果嗎?
當然是可以的,把任務做成 list (迭代) 的樣子就可以了!
範例程式碼 (使用 apply_async 達到 map 的效果)
import multiprocessing as mp
def task(num):
return 'The pool return result: ' + str(num)
if __name__=='__main__':
pool = mp.Pool()
res_list = []
for i in range(10):
res_list.append(pool.apply_async(task, (i,)))
for res in res_list:
print(res.get())
運行結果
說明
可以看到結果都是一樣的 (輸出方式稍微改變一下我們先不管)。
你可能會想問? 那 apply_async 與 map 到底差在哪?????
我們直接講重點:
兩種情況各有各自使用的地方,請依照自己的需求選擇使用。
- map:會等待 map 的任務執行完後,才執行接下來的主程式
- apply_async:不等待 apply_async 的任務執行完,就會執行接下來的主程式
範例程式碼 – apply_async 與 map 的差別
以 map 執行數到 100 的任務 (會等待才做 main 下一行)
import multiprocessing as mp
def task(num):
for i in range(num):
print(i)
return 'The pool return result: ' + str(num)
if __name__=='__main__':
pool = mp.Pool()
res = (pool.map(task, (100,)))
print("map wait the process finished.")
print(res)
執行結果 (只截重點,可以看到 map 等到任務完成才做)
以 apply_async 執行數到 100 的任務 (不會等待才做 main 下一行)
import multiprocessing as mp
def task(num):
for i in range(num):
print(i)
return 'The pool return result: ' + str(num)
if __name__=='__main__':
pool = mp.Pool()
res = pool.apply_async(task, (100,))
print("apply_async do NOT wait the process finished.")
print(res.get())
執行結果 (只截重點,可以看到 apply_async 不會等到任務完成才做)
結論
我們知道 apply_async 與 map 的差別後,
實際上,我們就看我們的任務,「main 有沒有需要等待才做下一行」,
就可以決定我們要使用 apply_async 還是 map 囉!
[…] […]