Рубрики
Без рубрики

Как написать параллельный код, используя будущее Python

Фьючерсы Python (параллелизм) TL; DR; Использование параллелизма для ускорения вещей довольно просты … Помечено Python, параллелизм, асинхронные, нити.

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_ _ACCOUNT . Это потому, что мы могли бы раннее вернуться Правда Если какой-либо услуг вернулся Правда Отказ

В нашем новом примере, используя параллелизм, мы всегда потребляя 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”