[英]multiprocessing.Pool: When to use apply, apply_async or map?
[英]why is more than one worker used in `multiprocessing.Pool().apply_async()`?
問題
從multiprocessing.Pool
文檔 :
apply_async(func ...)
:apply()
方法的一種變體,它返回一個結果對象。 ...
進一步閱讀...
apply(func[, args[, kwds]])
:使用參數args和關鍵字參數kwds調用func。 它會阻塞直到結果准備就緒。 給定此塊,apply_async()更適合於並行執行工作。 此外,func僅在池的工作程序之一中執行。
最后的粗線表明只使用了池中的一個工人。 我發現這僅在某些條件下是正確的。
特定
這是在三種類似情況下執行Pool.apply_async()
代碼。 在所有情況下,都會打印進程ID。
import os
import time
import multiprocessing as mp
def blocking_func(x, delay=0.1):
"""Return a squared argument after some delay."""
time.sleep(delay) # toggle comment here
return x*x, os.getpid()
def apply_async():
"""Return a list applying func to one process with a callback."""
pool = mp.Pool()
# Case 1: From the docs
results = [pool.apply_async(os.getpid, ()) for _ in range(10)]
results = [res.get(timeout=1) for res in results]
print("Docs :", results)
# Case 2: With delay
results = [pool.apply_async(blocking_func, args=(i,)) for i in range(10)]
results = [res.get(timeout=1)[1] for res in results]
print("Delay :", results)
# Case 3: Without delay
results = [pool.apply_async(blocking_func, args=(i, 0)) for i in range(10)]
results = [res.get(timeout=1)[1] for res in results]
print("No delay:", results)
pool.close()
pool.join()
if __name__ == '__main__':
apply_async()
結果
文檔中的示例(案例1)確認僅運行了一個工作程序。 在接下來的情況下,我們通過應用blocking_func
擴展此示例,該操作會延遲一段時間。
在blocking_func()
注釋time.sleep()
行會使所有情況一致。
# Time commented
# 1. Docs : [8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208]
# 2. Delay : [8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208]
# 3. No delay: [8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208]
每次對apply_async()
調用apply_async()
創建一個新的進程池,這就是為什么新的進程ID與后者不同的原因。
# Time uncommented
# 1. Docs : [6780, 6780, 6780, 6780, 6780, 6780, 6780, 6780, 6780, 6780]
# 2. Delay : [6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112]
# 3. No delay: [6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112]
但是,如果對time.sleep()
取消注釋,即使延遲為零,也將使用多個工作程序。
簡而言之,如案例1所示,我們期望有一名工人,但如案例2和3所示,我們得到多名工人。
題
雖然我希望通過只使用一個工作者Pool().apply_async()
為什么人總比一個人時使用多個time.sleep()
是取消注釋? 阻塞是否應該甚至影響apply
或apply_async
使用的工作程序數量?
注意:先前的相關問題會問“為什么只雇用一名工人?” 這個問題提出了相反的問題-“為什么不只雇用一個工人?” 我在Windows計算機上使用2個內核。
您的困惑似乎來自於認為[pool.apply_async(...) for i in range(10)]
是一個調用,而實際上有十個獨立調用。 調用任何池方法都是一種“工作”。 一項工作通常可以導致分配一個或多個任務。 apply
-方法總是在引擎蓋下只生產單一的任務。 任務是一個不可分割的工作單元,將由一個隨機的池工作人員整體接收。
只有一個共享inqueue
,所有工作人員都受夠了。 哪個空閑工作者將從等待隊列中的get()
喚醒,取決於操作系統。 情況1的結果熵仍然有些令人驚訝,並且可能非常幸運,至少除非您確認只有兩個核心。
是的,您對運行的觀察也受任務所需的計算時間的影響,因為線程(進程中的計划執行單元)通常使用時間切片策略進行計划(例如,對於Windows為〜20ms)。
該呼叫僅使用一名工人。 單個apply_async
不能在兩個工作apply_async
中執行。 這不會阻止在不同的工作程序中執行多個apply_async
調用。 這樣的限制將完全與擁有進程池的點背道而馳。
在@Darkonaut的評論的刺激下,我進一步檢查了一下,發現阻塞功能太快了。 我使用新的密集阻止功能測試了修改后的代碼。
碼
新的阻塞函數迭代計算斐波那契數。 可以傳入一個可選參數以擴大范圍並計算更大的數字。
def blocking_func(n, offset=0):
"""Return an intensive result via Fibonacci number."""
n += offset
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a, os.getpid()
def blocking_func(n, offset=0):
"""Return an intensive result via recursive fibonacci number."""
func = blocking_func
n += offset
if n <= 1:
return n, os.getpid()
return func(n-1) + func(n-2)
if __name__ == '__main__':
start = time.time()
apply_async()
end = time.time()
print(f"Duration : {end - start:.2f}s")
演示
將大整數( 100000
)傳遞給offset參數,例如...[pool.apply_async(blocking_func, args=(i, 100000)) ...]
並運行代碼,我們可以更可靠地觸發進程切換。
# Results
Docs : [10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032]
Offset : [10032, 8268, 10032, 8268, 10032, 8268, 10032, 8268, 10032, 8268]
Duration : 1.67s
有趣的是,在不到2秒的時間內,異步計算了10萬斐波納契數10次。 相比之下,使用Fibonacci的遞歸實現在大約30次迭代(未顯示)時會比較密集。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.