Nie nazwałbym concurrent.futures
bardziej „zaawansowanym” - jest to prostszy interfejs, który działa tak samo, niezależnie od tego, czy używasz wielu wątków, czy wielu procesów jako podstawowej sztuczki równoległej.
Tak więc, podobnie jak praktycznie we wszystkich przypadkach „prostszego interfejsu”, występują takie same kompromisy: ma płytszą krzywą uczenia się, w dużej mierze dlatego, że jest o wiele mniej dostępnych do nauczenia; ale ponieważ oferuje mniej opcji, może ostatecznie frustrować Cię w sposób, w jaki bogatsze interfejsy nie będą.
Jeśli chodzi o zadania związane z procesorem, to jest to zbyt mało zdefiniowane, aby powiedzieć, że ma dużo znaczenia. W przypadku zadań związanych z procesorem w CPythonie, potrzebujesz wielu procesów, a nie wielu wątków, aby mieć szansę na przyspieszenie. Ale to, ile (jeśli w ogóle) uzyskasz przyspieszenia, zależy od szczegółów sprzętu, systemu operacyjnego, a zwłaszcza od tego, ile komunikacji między procesami wymagają określone zadania. Pod okładkami wszystkie sztuczki zrównoleglania między procesami opierają się na tych samych prymitywach systemu operacyjnego - interfejs API wysokiego poziomu, którego używasz, nie jest głównym czynnikiem wpływającym na prędkość końcową.
Edycja: przykład
Oto ostateczny kod pokazany w artykule, do którego się odwołałeś, ale dodaję instrukcję importu potrzebną do jej działania:
from concurrent.futures import ProcessPoolExecutor
def pool_factorizer_map(nums, nprocs):
# Let the executor divide the work among processes by using 'map'.
with ProcessPoolExecutor(max_workers=nprocs) as executor:
return {num:factors for num, factors in
zip(nums,
executor.map(factorize_naive, nums))}
Oto dokładnie to samo, używając multiprocessing
zamiast tego:
import multiprocessing as mp
def mp_factorizer_map(nums, nprocs):
with mp.Pool(nprocs) as pool:
return {num:factors for num, factors in
zip(nums,
pool.map(factorize_naive, nums))}
Zwróć uwagę, że możliwość użycia multiprocessing.Pool
obiektów jako menedżerów kontekstu została dodana w Pythonie 3.3.
Z którym łatwiej się pracuje? LOL ;-) Są zasadniczo identyczne.
Jedna różnica polega na tym, że Pool
obsługuje tak wiele różnych sposobów robienia rzeczy, że możesz nie zdawać sobie sprawy, jak łatwo może to być być, dopóki nie wespniesz się dość daleko po krzywej uczenia się.
Ponownie, wszystkie te różne sposoby są zarówno mocną, jak i słabą stroną. Są mocną stroną, ponieważ w niektórych sytuacjach może być wymagana elastyczność. Są słabością ze względu na „najlepiej tylko jeden oczywisty sposób na zrobienie tego”. Projekt trzymający się wyłącznie (jeśli to możliwe) concurrent.futures
będzie prawdopodobnie łatwiejszy w utrzymaniu w dłuższej perspektywie, ze względu na brak darmowej nowości w sposobie wykorzystania jego minimalnego API.
ProcessPoolExecutor
rzeczywistości ma więcej opcji niżPool
dlatego, żeProcessPoolExecutor.submit
zwracaFuture
instancje, które umożliwiają cancellation (cancel
), sprawdzanie, który wyjątek został zgłoszony (exception
) i dynamiczne dodawanie wywołania zwrotnego do wywołania po zakończeniu (add_done_callback
). Żadna z tych funkcji nie jest dostępna wAsyncResult
przypadku wystąpień zwróconych przezPool.apply_async
. W inny sposóbPool
ma więcej opcji tytułuinitializer
/initargs
,maxtasksperchild
orazcontext
wPool.__init__
oraz więcej metod narażone przezPool
instancji.Pool
, chodziło o moduły.Pool
jest niewielką częścią tego, co jest w dokumentachmultiprocessing
i jest tak daleko w dokumentach, że ludzie potrzebują trochę czasu, zanim zdają sobie sprawę, że w ogóle istniejemultiprocessing
. Ta konkretna odpowiedź skupiła się naPool
tym, że jest to cały artykuł, do którego odwoływał się program operacyjny, i którycf
jest „znacznie łatwiejszy w obsłudze”, po prostu nie jest prawdą w odniesieniu do tego, o czym rozmawialiśmy. Poza tym,cf
nic nieas_completed()
może być również bardzo przydatny.