Асинхронное программирование на Python 3

Асинхронное программирование на Python 3

От автора: в этом руководстве вы познакомитесь с функциями асинхронного ввода-вывода, представленными в Python 3.4 и улучшенными в Python 3.5 и 3.6.

Ранее в Python было мало вариантов для асинхронного программирования. Новая функция асинхронного ввода-вывода, наконец, обеспечивает необходимую поддержку, которая включает в себя как высокоуровневые API, так и стандартную поддержку, нацеленную на объединение нескольких сторонних решений (Twisted, Gevent, Tornado, asyncore и т.д.).

Важно понимать, что изучение асинхронного ввода-вывода Python не является простой задачей из-за скоростной итерации, области действия и необходимости обеспечить способ миграции на существующие асинхронные среды. Я сосредоточусь на последних и самых простых аспектах.

Существует множество вещей, которые интересным образом взаимодействуют через границы потоков, границы процессов и удаленные машины. Существуют отличия и ограничения для конкретной платформы. Давайте разберемся в этом.

Подключаемый цикл событий

Основной концепцией асинхронного ввода-вывода является цикл обработки событий. В программе может быть несколько циклов событий. Каждый поток будет иметь не более одного активного цикла обработки событий. Цикл обработки событий предоставляет следующие возможности:

Регистрация, выполнение и отмена отложенных вызовов (с задержками).

Создание клиентских и серверных транспортов для различных видов связи.

Запуск подпроцессов и связанных транспортов для связи с внешней программой.

Делегирование ресурсозатратных вызовов функций в пул потоков.

Вот простой пример, в котором запускаются две сопрограммы и вызывается функция с задержкой. Он показывает, как использовать цикл обработки событий для работы вашей программы:

Класс AbstractEventLoop обеспечивает базовое соглашение для циклов событий. Есть много вещей, которые должен поддерживать цикл обработки событий:

Планирование функций и сопрограмм для выполнения

Создание фьючерсов и задач

Управление TCP-серверами

Обработка сигналов (в Unix)

Работа с пайпами и подпроцессами

Вот методы, связанные с запуском и остановкой события, а также планированием функций и сопрограмм:

Подключение нового цикла событий

Asyncio предназначен для поддержки нескольких реализаций циклов событий, которые придерживаются его API. Ключ — это класс EventLoopPolicy, который настраивает asyncio и позволяет контролировать каждый аспект цикла событий. Вот пример пользовательского цикла обработки событий uvloop , основанного на libuv, который должен быть намного быстрее, чем его альтернативы (я не тестировал его сам):

Вот и все. Теперь, когда вы используете любую функцию asyncio, она запускает uvloop под капотом.

Сопрограммы, фьючерсы и задачи

Сопрограмма — это составной термин. Это функция, которая выполняется асинхронно, и объект, который необходимо запланировать. Вы определяете их, добавляя ключевое слово async перед определением:

Если вы вызовите такую функцию, она не запустится. Вместо этого она возвращает объект сопрограммы, и если вы не запланируете его выполнение, вы также получите предупреждение:

Чтобы на самом деле выполнить сопрограмму, нам нужен цикл обработки событий:

Это прямое планирование. Вы также можете поставить в очередь сопрограммы. Обратите внимание, что при вызове сопрограмм вы должны вызывать await:

Класс asyncio Future аналогичен классу concurrent.future.Future. Он не является потокобезопасным и поддерживает следующие функции:

добавление и удаление готовых обратных вызовов

отмена

настройка результатов и исключений

Вот как использовать фьючерсы с циклом событий. Сопрограмма take_your_time() принимает фьючерс и устанавливает свой результат после простоя в течение секунды.

Функций ensure_future() планирует сопрограмму, а wait_until_complete() ожидает выполнения фьючерса. Под капотом она добавляет готовый обратный вызов к фьючерсу.

Это довольно громоздко. Asyncio предлагает задачи, чтобы сделать работу с фьючерсами и сопрограммами более простой. Задача — это подкласс Future, который оборачивает сопрограмму и его можно отменить.

Сопрограмма не должна принимать явный фьючерс и устанавливать его результат или исключение. Вот как выполнить те же операции с помощью задачи:

Транспорты, протоколы и потоки

Транспорт — это абстракция канала взаимодействия. Транспорт всегда поддерживает определенный протокол. Asyncio предоставляет встроенные реализации для TCP, UDP, SSL и каналов подпроцесса.

Если вы знакомы с сетевым программированием на основе сокетов, вам будет легко освоить транспорты и протоколы. С Asyncio вы получаете стандартное асинхронное сетевое программирование. Давайте рассмотрим печально известный echo-сервер и клиент («hello world» сетевого программирования).

Во-первых, клиент echo реализует класс с именем EchoClient, который является производным от asyncio.Protocol. Он сохраняет свой цикл событий и сообщение, которое он отправляет на сервер при подключении.

При обратном вызове connection_made() он записывает сообщение в транспорт. В методе data_received() он просто выводит ответ сервера, а в методе connection_lost() останавливает цикл обработки событий. При передаче экземпляра класса EchoClient в метод цикла create_connection(), результатом является сопрограмма, которую цикл выполняет, пока не завершится.

Сервер работает аналогично за исключением того, что он работает вечно, ожидая подключения клиентов. После отправки Echo-ответа он также закрывает соединение с клиентом и готов к подключению следующего клиента.

Новый экземпляр EchoServer создается для каждого соединения, поэтому даже если несколько клиентов подключаются одновременно, проблемы конфликтов с атрибутом transport не возникнет.

Вот результат после подключения двух клиентов:

Потоки предоставляют высокоуровневый API, который основан на сопрограммах и предоставляет абстракции Reader и Writer. Протоколы и транспорты скрыты, нам нет необходимости определять собственные классы, и нет обратных вызовов. Вы просто ожидаете такие события, как соединение и получение данных.

Клиент вызывает функцию open_connection(), которая возвращает объекты Reader и Writer. Чтобы закрыть соединение, он закрывает Reader.

Сервер также значительно упрощен.

Работа с подпроцессами

Asyncio также охватывает взаимодействия с подпроцессами. Следующая программа запускает другой процесс Python и выполняет код «import this». Это одна из знаменитых пасхалок Python, она выводит «Zen of Python». Проверьте вывод ниже.

Процесс Python запускается в сопрограмме zen() с использованием функции create_subprocess_exec() и связывает стандартный вывод с пайпом. Затем он перебирает стандартные выходные строки, используя await, чтобы дать возможность выполнить другие процессы или сопрограммы, если выходные данные еще не готовы.

Обратите внимание, что в Windows вы должны установить цикл обработки событий ProactorEventLoop , потому что стандартный SelectorEventLoop не поддерживает пайпы.

Заключение

Python asyncio — это комплексная среда для асинхронного программирования. Она применима в разных областях и поддерживает как низкоуровневые, так и высокоуровневые API. Она все еще относительно молода и не достаточно хорошо изучена сообществом.

Я уверен, что со временем появятся лучшие практики и новые примеры, которые облегчат использование этой мощной библиотеки.

Автор: Gigi Sayfan

Источник: //code.tutsplus.com

Редакция: Команда webformyself.

Метки:

Похожие статьи:

Комментарии Вконтакте: