簡體   English   中英

為什么在multiprocessing.Pool()。apply_async()中使用了多個工人?

[英]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()是取消注釋? 阻塞是否應該甚至影響applyapply_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM