簡體   English   中英

與 multiprocessing.Pool 共享一個計數器

[英]Sharing a counter with multiprocessing.Pool

我想使用multiprocessing.Value + multiprocessing.Lock在不同的進程之間共享一個計數器。 例如:

import itertools as it
import multiprocessing

def func(x, val, lock):
    for i in range(x):
        i ** 2
    with lock:
        val.value += 1
        print('counter incremented to:', val.value)

if __name__ == '__main__':
    v = multiprocessing.Value('i', 0)
    lock = multiprocessing.Lock()

    with multiprocessing.Pool() as pool:
        pool.starmap(func, ((i, v, lock) for i in range(25)))
    print(counter.value())

這將引發以下異常:

RuntimeError:同步對象只能通過繼承在進程之間共享

我最困惑的是一個相關的(雖然不是完全類似的)模式與multiprocessing.Process()

if __name__ == '__main__':
    v = multiprocessing.Value('i', 0)
    lock = multiprocessing.Lock()

    procs = [multiprocessing.Process(target=func, args=(i, v, lock))
             for i in range(25)]
    for p in procs: p.start()
    for p in procs: p.join()

現在,我認識到這是兩件明顯不同的事情:

  • 第一個示例使用的工作進程數等於cpu_count() ,並在它們之間拆分可迭代range(25)
  • 第二個示例創建了 25 個工作進程和任務,每個進程和任務都有一個輸入

也就是說:如何以這種方式與pool.starmap() (或pool.map() )共享實例?

我在這里這里這里看到過類似的問題,但這些方法似乎不適合.map() / .starmap() ,不管Value是否使用ctypes.c_int


我意識到這種方法在技術上有效:

def func(x):
    for i in range(x):
        i ** 2
    with lock:
        v.value += 1
        print('counter incremented to:', v.value)

v = None
lock = None

def set_global_counter_and_lock():
    """Egh ... """
    global v, lock
    if not any((v, lock)):
        v = multiprocessing.Value('i', 0)
        lock = multiprocessing.Lock()

if __name__ == '__main__':
    # Each worker process will call `initializer()` when it starts.
    with multiprocessing.Pool(initializer=set_global_counter_and_lock) as pool:
        pool.map(func, range(25))

這真的是解決此問題的最佳實踐方式嗎?

使用Pool時遇到的RuntimeError是因為池方法的參數在通過(池內部)隊列發送到工作進程之前被腌制。 您嘗試使用哪種池方法在這里無關緊要。 當您只使用Process時不會發生這種情況,因為不涉及隊列。 您可以僅使用pickle.dumps(multiprocessing.Value('i', 0))重現錯誤。

您的最后一個代碼片段並不像您認為的那樣工作。 您沒有共享Value ,而是為每個子進程重新創建獨立的計數器。

如果您在 Unix 上並使用默認啟動方法“fork”,您只需將共享對象作為參數傳遞到池方法中即可。 您的子進程將通過分叉繼承全局變量。 使用 process-start-methods “spawn”(默認 Windows 和macOS with Python 3.8+ )或“forkserver”,您必須在Pool實例化期間使用initializer ,讓子進程繼承共享對象。

請注意,這里不需要額外的multiprocessing.Lock ,因為multiprocessing.Value默認帶有一個您可以使用的內部。

import os
from multiprocessing import Pool, Value #, set_start_method


def func(x):
    for i in range(x):
        assert i == i
        with cnt.get_lock():
            cnt.value += 1
            print(f'{os.getpid()} | counter incremented to: {cnt.value}\n')


def init_globals(counter):
    global cnt
    cnt = counter


if __name__ == '__main__':

    # set_start_method('spawn')

    cnt = Value('i', 0)
    iterable = [10000 for _ in range(10)]

    with Pool(initializer=init_globals, initargs=(cnt,)) as pool:
        pool.map(func, iterable)

    assert cnt.value == 100000

可能還值得注意的是,您不需要在所有情況下都共享計數器。 如果您只需要跟蹤某事總共發生的頻率,一個選項是在計算期間保留單獨的工作人員本地計數器,並在最后匯總。 對於在並行計算本身期間不需要同步的頻繁計數器更新,這可能會顯着提高性能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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