[英]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)
也就是說:如何以這種方式與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.