Concurrent.futures vs Multiprocessing в Python 3


148

Python 3.2 представив паралельні ф'ючерси , які, здається, є деякою вдосконаленою комбінацією старих модулів різьблення та багатообробки .

Які переваги та недоліки використання цього для завдань, пов'язаних з процесором, у порівнянні зі старим багатопроцесорним модулем?

Ця стаття говорить про те, що з ними набагато простіше працювати - це так?

Відповіді:


145

Я б не називав concurrent.futuresбільш «просунутим» - це простіший інтерфейс, який працює майже однаково, незалежно від того, використовуєте ви декілька потоків або декілька процесів в якості основної прихилки паралелізації.

Таким чином, як і практично у всіх випадках «простішого інтерфейсу», задіяні такі ж компроміси: він має більш дрібну криву навчання, значною мірою лише тому, що є так багато менш доступного для вивчення; але, оскільки він пропонує менше варіантів, це може врешті зривати вас тим, що багатші інтерфейси не стануть.

Що стосується завдань, пов'язаних з процесором, це занадто недостатньо вказано, щоб сказати, що це має велике значення. Для завдань, пов'язаних з процесором під CPython, вам потрібно кілька процесів, а не декілька потоків, щоб мати шанс отримати швидкість. Але кількість (якщо така є) швидкості, яку ви отримаєте, залежить від деталей вашого обладнання, вашої ОС і, особливо, від того, наскільки міжпроцесорне спілкування потребує ваших конкретних завдань. Під обкладинками всі мішки міжпроцесорної паралелізації паралелізуються одними і тими ж примітивами ОС - API високого рівня, який ви використовуєте, щоб отримати на них, не є основним фактором швидкості нижньої лінії.

Правка: приклад

Ось кінцевий код, показаний у статті, на яку ви посилаєтесь, але я додаю заяву про імпорт, необхідну для роботи:

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))}

Ось саме те, що використовується multiprocessingзамість цього:

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))}

Зауважте, що можливість використання multiprocessing.Poolоб'єктів як менеджерів контексту була додана в Python 3.3.

З яким легше працювати? LOL ;-) Вони по суті однакові.

Одна відмінність полягає в тому, що Poolпідтримується так багато різних способів робити речі, що ви можете не усвідомлювати, наскільки це легко, доки ви не піднялися зовсім на криву навчання.

Знову ж таки, всі ці різні способи є і силою, і слабкістю. Вони сильні, оскільки в деяких ситуаціях може знадобитися гнучкість. Вони слабкі, оскільки "бажано лише один очевидний спосіб зробити це". Проект, який дотримується виключно (якщо це можливо) concurrent.futures, мабуть, буде простішим у підтримці в довгостроковій перспективі, через відсутність безкоштовної новизни в тому, як можна використовувати його мінімальний API.


20
"Вам потрібні кілька процесів, а не декілька потоків, щоб мати будь-який шанс отримати прискорення" є надто суворим. Якщо важлива швидкість; код може вже використовувати бібліотеку С, і тому він може випустити GIL, наприклад, regex, lxml, numpy.
jfs

4
@JFSebastian, дякую, що додав, що, можливо, я мав би сказати "під чистим CPython", але я боюся, що тут немає короткого способу пояснити правду, не обговорюючи GIL.
Тім Пітерс

2
І варто згадати, що потоки можуть бути особливо корисними та досить при роботі з довгим IO.
kotrfa

9
@TimPeters Деяким чином ProcessPoolExecutorнасправді є більше варіантів, ніж Poolтому, що ProcessPoolExecutor.submitповертає Futureекземпляри, які дозволяють скасувати ( cancel), перевірити, який виняток був піднятий ( exception), і динамічно додавати зворотний виклик, який потрібно викликати після завершення ( add_done_callback). Жодна з цих функцій не доступна із AsyncResultповерненими екземплярами Pool.apply_async. В інших відносинах Poolмає більше можливостей з - за initializer/ initargs, maxtasksperchildі contextв Pool.__init__, і більше способів , що надаються , Poolнаприклад.
макс

2
@max, звичайно, але зауважте, що питання не про те Pool, а про модулі. Poolце невелика частина того, що є multiprocessing, і так далеко в документах, що люди потребують певного часу, щоб зрозуміти, що це навіть існує multiprocessing. Ця конкретна відповідь була зосереджена на Poolтому, що це вся стаття, з якою використана ОП, і що cf"набагато простіше працювати", просто не відповідає тому, що обговорювалась у статті. Крім того, cf«s as_completed()також може бути дуже зручно.
Тім Пітерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.