Muhtemelen paralel işlemeye ihtiyaç duyduğunuzda çoğu zaman, ya modüldeki ProcessPoolExecutorsınıfın ya da concurrent.futuresmodüldeki Poolsınıfın multiprocessingeşdeğer kolaylıklar sağlayacağını ve kişisel tercih meselesine dönüştüğünü göreceksiniz . Ancak her biri, belirli işlemleri daha kolay hale getiren bazı olanaklar sunar. Sadece bir çift göstereceğimi düşündüm:
Bir grup görevi gönderirken, bazen görev sonuçlarını (yani değerleri döndürür) kullanılabilir olur olmaz almak istersiniz. Her iki tesis de, gönderilen bir görevin sonucunun geri arama mekanizmaları aracılığıyla kullanılabilir olduğuna dair bildirim sağlar:
Çoklu işlemeyi kullanma.Havuz:
import multiprocessing as mp
def worker_process(i):
return i * i
def process_result(return_value):
print(return_value)
def main():
pool = mp.Pool()
for i in range(10):
pool.apply_async(worker_process, args=(i,), callback=process_result)
pool.close()
pool.join()
if __name__ == '__main__':
main()
Aynısı, garip bir şekilde de olsa, aşağıdakilerle bir geri arama kullanılarak yapılabilir concurrent.futures:
import concurrent.futures
def worker_process(i):
return i * i
def process_result(future):
print(future.result())
def main():
executor = concurrent.futures.ProcessPoolExecutor()
futures = [executor.submit(worker_process, i) for i in range(10)]
for future in futures:
future.add_done_callback(process_result)
executor.shutdown()
if __name__ == '__main__':
main()
Burada her görev ayrı ayrı gönderilir ve bunun için bir Futureörnek döndürülür. Daha sonra geri arama Future,. Son olarak, geri çağırma çağrıldığında, geçirilen bağımsız değişken, Futuretamamlanan görevin örneğidir resultve gerçek dönüş değerini almak için yöntemin çağrılması gerekir. Ancak concurrent.futuresmodül ile, aslında bir geri aramayı kullanmaya gerek yoktur. as_completedYöntemi kullanabilirsiniz :
import concurrent.futures
def worker_process(i):
return i * i
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
futures = [executor.submit(worker_process, i) for i in range(10)]
for future in concurrent.futures.as_completed(futures):
print(future.result())
if __name__ == '__main__':
main()
Ve örnekleri worker_processtutmak için bir sözlük kullanarak , dönüş değerini orijinal geçirilen bağımsız değişkene geri bağlamak kolaydır Future:
import concurrent.futures
def worker_process(i):
return i * i
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
futures = {executor.submit(worker_process, i): i for i in range(10)}
for future in concurrent.futures.as_completed(futures):
i = futures[future]
print(i, future.result())
if __name__ == '__main__':
main()
Ancak aşılmaması için, multiprocessing.Poolgörev sonuçlarının tamamlanırken işlenmesine izin veren bir yöntemi vardır:
import multiprocessing as mp
def worker_process(i):
return i * i
def main():
cpu_count = mp.cpu_count()
N = 100
chunk_size = N // cpu_count
with mp.Pool() as pool:
for result in pool.imap_unordered(worker_process, range(N), chunksize=chunk_size):
print(result)
if __name__ == '__main__':
main()
Ancak imap_unordered, çalışan işlem dönüş değeriyle birlikte orijinal çağrı bağımsız değişkenlerini döndürmedikçe, bir sonucu gönderilen bir işle kolayca bağlamanın bir yolu yoktur. Öte yandan, sonuçların öngörülebilir bir sırada olacağı bir chunksizeile imap_unorderdve belirtme yeteneği , bu yöntemleri , esasen 1'lik bir yığın boyutu kullanan sınıfın yönteminden daha verimli hale getirebilir .imapsubmitconcurrent.futures.ProcessPoolExector
multiprocessing.PoolSınıf bir yöntemi vardır applysonuç hazır olana kadar havuz ve bloklar bir görev gönderir. Dönüş değeri, işleve aktarılan işçi işlevinin yalnızca dönüş değeridir apply. Örneğin:
import multiprocessing as mp
def worker_process(i):
return i * i
def main():
with mp.Pool() as pool:
print(pool.apply(worker_process, args=(6,)))
print(pool.apply(worker_process, args=(4,)))
if __name__ == '__main__':
main()
concurrent.futures.ThreadPoolExecutorSınıf böyle bir eşdeğeri yer alır. Döndürülen örneğe bir submitve ardından bir çağrı yapmalısınız . Bunu yapmak zor değil, ancak yöntem, engelleyici bir görev gönderiminin uygun olduğu kullanım durumu için daha uygundur. Böyle bir durum, iş parçacığı için çağrıları işlediğiniz zamandır çünkü iş parçacıklarında yapılan işlerin çoğu, CPU'ya çok bağlı olan bir işlev haricinde yoğun bir şekilde I / O'dur. Evreleri oluşturan ana program önce bir örnek yaratır ve bunu tüm evrelere argüman olarak iletir. İş parçacığının yoğun şekilde CPU'ya bağlı işlevi çağırması gerektiğinde, şimdi işlevi kullanarakresultFuturepool.applymultiprocessing.Poolpool.apply yöntem böylece kodu başka bir işlemde çalıştırır ve diğer iş parçacıklarının çalışmasına izin vermek için mevcut işlemi serbest bırakır.
concurrent.futuresModülün iki sınıfa ProcessPoolExecutorve ThreadPoolExecutoraynı arayüzlere sahip olması büyük bir anlaşma yapıldı . Bu güzel bir özellik. Ancak multiprocessingmodül, aşağıdakilerle ThreadPoolaynı arayüze sahip belgelenmemiş bir sınıfa sahiptir Pool:
>>> from multiprocessing.pool import Pool
>>> from multiprocessing.pool import ThreadPool
>>> dir(Pool)
['Process', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_check_running', '_get_sentinels', '_get_tasks', '_get_worker_sentinels', '_guarded_task_generation', '_handle_results', '_handle_tasks', '_handle_workers', '_help_stuff_finish', '_join_exited_workers', '_maintain_pool', '_map_async', '_repopulate_pool', '_repopulate_pool_static', '_setup_queues', '_terminate_pool', '_wait_for_updates', '_wrap_exception', 'apply', 'apply_async', 'close', 'imap', 'imap_unordered', 'join', 'map', 'map_async', 'starmap', 'starmap_async', 'terminate']
>>> dir(ThreadPool)
['Process', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_check_running', '_get_sentinels', '_get_tasks', '_get_worker_sentinels', '_guarded_task_generation', '_handle_results', '_handle_tasks', '_handle_workers', '_help_stuff_finish', '_join_exited_workers', '_maintain_pool', '_map_async', '_repopulate_pool', '_repopulate_pool_static', '_setup_queues', '_terminate_pool', '_wait_for_updates', '_wrap_exception', 'apply', 'apply_async', 'close', 'imap', 'imap_unordered', 'join', 'map', 'map_async', 'starmap', 'starmap_async', 'terminate']
>>>
ProcessPoolExecutor.submitBir Futureörnek döndüren veya Pool.apply_asyncbir AsyncResultörnek döndüren ve sonucu almak için bir zaman aşımı değeri belirleyen görevler ile görev gönderebilirsiniz :
from concurrent.futures import ProcessPoolExecutor, TimeoutError
from time import sleep
def worker_1():
while True:
print('hanging')
sleep(1)
def main():
with ProcessPoolExecutor(1) as pool:
future = pool.submit(worker_1)
try:
future.result(3)
except TimeoutError:
print('timeout')
if __name__ == '__main__':
main()
print("return from main()")
Baskılar:
hanging
hanging
hanging
timeout
hanging
hanging
hanging
hanging
hanging
hanging
hanging
etc.
Gönderilen görev bu süre içinde tamamlanmadığından , arama sırasındaki ana işlem 3 saniye sonra future.result(3)bir TimeoutErroristisna alır. Ancak görev çalışmaya devam ediyor, süreci bağlar ve with ProcessPoolExecutor(1) as pool:blok asla çıkmaz ve bu nedenle program sona ermez.
from multiprocessing import Pool, TimeoutError
from time import sleep
def worker_1():
while True:
print('hanging')
sleep(1)
def main():
with Pool(1) as pool:
result = pool.apply_async(worker_1, args=())
try:
result.get(3)
except TimeoutError:
print('timeout')
if __name__ == '__main__':
main()
print("return from main()")
Baskılar:
hanging
hanging
hanging
timeout
return from main()
Ancak bu sefer, zaman aşımına uğrayan görev hala çalışmaya devam ediyor ve işlemi yazıyor olsa bile, withbloğun çıkması engellenmez ve bu nedenle program normal şekilde sona erer. Avantaj gidecek gibi görünüyor multiprocessing.Pool.