TL; DR;
Использование параллелизма для ускорения вещей довольно прост в Python, используя chapulent.futures
модуль. Тем не менее, это не серебряная пуля, и нужно знать Когда использовать его.
Все началось с генераторов …
В предыдущем сообщении я упомянул, как вы можете использовать генераторы Python как способ избежать выполнения дополнительных вызовов сервисов, сохраняя некоторое время и ресурсы. Вот сводка:
# Suppose you want to check if a given user_email is registered in # any of the following social media: Facebook, Github, or Twitter. def has_facebook_account(user_email): ... def has_github_account(user_email): ... def has_twitter_account(user_email): ... def has_social_account(user_email): calls = [ has_facebook_account, has_github_account, has_twitter_account, ] return any((call(user_email) for call in calls)) # (expr for i in iterable) syntax denotes a generator comprehension
Довольно просто, а? любой
итерат и оценивает каждый Звоните (user_email)
доход от генератора до тех пор, пока один из них не возвращается Правда
Отказ Это известно как раннее возвращение – Вы в основном сохраняете некоторое время и ресурсы, не выполняющие ненужные вызовы.
Некоторые люди дали хорошие обратной связи, упомянув, что я должен сделать это одновременно , то есть, я мог сделать Все звонки одновременно и Раннее возвращение Как только любой Позвоните вернулся правда. «Это хорошая идея», я думал. Я рад, что есть умнее, чем у меня сам.
Если это не понятно Почему Я хотел бы сделать это: предположим has_facebook_account
занимает слишком много времени, чтобы запустить (так как обычно происходит с любыми операциями ввода/вывода и сетевых операций из-за высокой задержки) и has_github_account.
довольно быстро (возможно, это кэшируется, например). Я бы всегда нужно ждать has_facebook_account
вернуть до Призыв has_github_account.
Так как предметы генератора будут оцениваться упорядоченный . Это не звучит весело.
Сделать его одновременно!
Я использую Питона chapulent.futures
модуль (Доступно с версии 3.2). Этот модуль состоит в основном двух объектов: Исполнитель
и Будущее
объект. Вы должны прочитать эту документацию, это действительно короткое и прямо к точке.
Исполнитель
Абстрактный класс несет ответственность за Планирование Задача (или Callable ) для выполнения асинхронно (или одновременно ). Планирование задачи Возвращает Будущее
Объект, который является ссылкой на задачу и представляет его государство – в ожидании, законченные или отменены Отказ
Если вы когда-либо работали с JavaScript обещает раньше, Будущее
очень похоже на Обещание
: Вы знаете, его исполнение будет в конце концов Будьте сделаны, но вы не можете знать, когда. Хорошая вещь: это Неблокирующий код , что означает, что интерпретатор Python не должен ждать до тех пор, пока выполнение запланированной задачи не заканчивается перед запуском следующей строки кода.
Таким образом, в нашем сценарии мы могли бы Расписание Три задача, один для запроса каждой платформы (Facebook, GitHub и Twitter) для адреса электронной почты пользователя. Таким образом, когда-то какая-либо из этих задач в конце концов Возвращает значение, я могу раннее возвращение Если значение это Правда
, Поскольку все, что мы хотим знать, это если у пользователя есть учетная запись на любой из этих платформ.
Разговор дешево. Покажите мне код.
Примерный код ниже легко следовать, но я настоятельно рекомендую вам прочитать комментарийные строки.
import time # I will use it to simulate latency with time.sleep from concurrent.futures import ThreadPoolExecutor, as_completed def has_facebook_account(user_email): time.sleep(5) # 5 seconds! That is bad. print("Finished facebook after 5 seconds!") return True def has_github_account(user_email): time.sleep(1) # 1 second. Phew! print("Finished github after 1 second!") return True def has_twitter_account(user_email): time.sleep(3) # Well... print("Finished twitter after 3 seconds!") return False # Main method that answers if a user has an account in any of the platforms def has_social_account(user_email): # ThreadPoolExecutor is a subclass of Executor that uses threads. # max_workers is the max number of threads that will be used. # Since we are scheduling only 3 tasks, it does not make sense to have # more than 3 threads, otherwise we would be wasting resources. executor = ThreadPoolExecutor(max_workers=3) # Schedule (submit) 3 tasks (one for each social account check) # .submit method returns a Future object facebook_future = executor.submit(has_facebook_account, user_email) twitter_future = executor.submit(has_twitter_account, user_email) github_future = executor.submit(has_github_account, user_email) future_list = [facebook_future, github_future, twitter_future] # as_completed receives an iterable of Future objects # and yields each future once it has been completed. for future in as_completed(future_list): # .result() returns the future object return value future_return_value = future.result() print(future_return_value) if future_return_value is True: # I can early return once any result is True return True user_email = "user@email.com" if __name__ == '__main__': has_social_account(user_email)
Finished github after 1 second! User has social account. # The created threads will still run until completion Finished twitter after 3 seconds! Finished facebook after 5 seconds!
Обратите внимание, что, хотя …| Facebook_future занимает больше двух других запланированных задач, чтобы закончить, однако, это не
Блок Исполнение – он продолжает работать по собственной теме. И хотя github_future Является ли последняя запланированная задача, это первое завершивание.
Краткое резюме
Будущее
это объект, который представляет запланированную задачу, которая будет в конце концов финиш.Исполнитель
Является ли планировщик задач (как только задача запланирована, она возвращает Aбудущее
объект).- Это может быть
ThreadPoolexecutor
илиProcessPoolExecutor
(Использование потоков VS процессов).
- Это может быть
- Можно использовать
Execututor.submit (Callable)
Чтобы запланировать задачу для проведения асинхронически. as_completed
получает ущерба дляБудущее
Объекты и возвращает генератор, где каждый полученный элемент является закончил задача.
Когда я не хочу использовать параллелизм тогда?
В качестве инженеров программного обеспечения наша работа не только зная Как использовать инструмент, но и Когда использовать его. Сетевые операции (и связанные ввода/выводные операции в целом) обычно являются хорошим местом для использования одновременного кода из-за их задержки. Но всегда есть компромисс …
В примере выше мы Отдается от производительности для использования ресурсов. Как? При использовании Генераторы , только худший сценарий в конечном итоге потребляет 3 службы – один звонок для каждого HAS_
. Это потому, что мы могли бы раннее вернуться Правда
Если какой-либо услуг вернулся Правда
Отказ
В нашем новом примере, используя параллелизм, мы всегда потребляя 3 сервиса – поскольку звонки сделаны асинхронно Отказ
«Ах, но это все еще может спасти нас много времени!», – говорите. Это зависит от Сервисы Вы употребляете. В примере выше я искусственно сделал has_facebook_account.
Действительно медленно – 5 раз медленнее, чем самая быстрая альтернатива. Но, если все услуги имели аналогичное время отклика, а если Сохранение ресурсов Было важно (предположим, что призыв каждая служба будет вызывать действительно тяжелый запрос в базе данных), например, используя Синхронный код может быть лучшим подходом.
Ради данных: Facebook имеет над 2,7 миллиарда ежемесячных активных пользователей , пока Твиттер имеет вокруг 330 миллионов и GitHub имеет просто 40 миллионов пользователей Отказ Итак, весьма вероятно, что звонит has_facebook_account
Сначала было бы достаточно в огромном большинстве сценариев, так как он вернется Правда
с гораздо более высокой частотой, чем другие услуги, таким образом, сохраняя множество ненужных вызовов.
Вывод
Знайте, как написать параллельный код, который довольно легко с фьючерсами Python. Но более важно: знать Когда сделать это. Есть случаи, когда увеличение производительности не окупает использование ресурсов.
Я настоятельно советую вам прочитать Документы на chapulent.futures
и Глава 17 на отличном свободном свободном роду Python Buciano Ramalho Отказ
Ах, а вот суть, если вы этого хотите!
Оригинал: “https://dev.to/gcrsaldanha/how-to-write-concurrent-code-using-python-s-future-39ij”