[Перевод] Полное руководство по модулю asyncio в Python. Часть 1

Python-модуль asyncio позволяет заниматься асинхронным программированием с применением конкурентного выполнения кода, основанного на корутинах. Хотя этот модуль имеется в Python уже много лет, он остаётся одним из самых интересных механизмов языка. Но asyncio, при этом, можно назвать ещё и одним из модулей, которые вызывают больше всего недоразумений. Дело в том, что начинающим разработчикам бывает трудно приступить к использованию asyncio.

0da7b01c17d1ae3019f4f11772e4f2d3.png

Перед вами — подробное и всестороннее руководство по использованию модуля asyncio в Python. В частности, здесь будут рассмотрены следующие основные вопросы:

  • Определение, создание и запуск корутин.

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

  • Организация неблокирующего ввода-вывода.

  • Средства конкурентного программирования, используемые при работе с корутинами.

Оригинал этого руководства включает в себя 26 разделов. Сегодня, в первой части перевода, мы предлагаем вашему вниманию разделы №1 и 2.

1. Что такое асинхронное программирование

Асинхронное программирование — это концепция программирования, при применении которой запуск длительных операций происходит без ожидания их завершения и не блокирует дальнейшее выполнение программы.

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

Прежде чем переходить к освоению asyncio — разберёмся с основами асинхронного программирования.

1.1. Асинхронные задачи

Слово «асинхронный» используют для описания неких процессов, не совпадающих друг с другом во времени. Его противоположность — слово «синхронный», используемое для описания чего-либо, точно совпадающего во времени.

Асинхронный — не одновременный или не параллельный во времени.

Словарь Merriam-Webster

В программировании смысл асинхронности в том, что когда делается запрос на выполнение некоего действия, это действие выполняется не в тот момент, когда делается запрос, а позже.

Асинхронный: раздельное выполнение потоков, которые могут выполняться конкурентно в любом порядке относительно друг друга, является асинхронным.

The Art of Concurrency, 2009, с. 265

Например, можно выполнять асинхронные вызовы функций.

При выполнении асинхронного вызова функции мы делаем запрос на выполнение вызова функции, но не ждём завершения этого вызова. А состояние вызванной функции или результаты её работы мы узнаём позже.

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

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

В этом — вся суть. Мы не осуществляем контроль над тем, как или когда будет обработан запрос. Мы лишь сообщаем системе о том, что нам нужно, чтобы запрос был бы обработан тогда, когда наша программа занимается какими-то другими делами.

Выполнение асинхронного вызова функции часто приводит к предоставлении вызывающей стороне некоего идентификатора запроса, который вызывающая сторона может использовать для того, чтобы проверить состояние запроса или получить результаты. Такие идентификаторы часто называют объектами, ждущими результата выполнения задачи (future, объект Future, преднамеченное значение, «футура»).

Объект future: идентификатор асинхронного вызова функции, позволяющий проверять состояние вызова и получать результаты работы функции.

Комбинацию асинхронного вызова функции и объекта Future часто называют асинхронной задачей. Это так из-за того, что такая комбинация даёт больше возможностей, чем обычный вызов функции. Например — позволяет отменять запрос и выполнять другие действия.

Асинхронная задача: этот термин используется для обозначения сущности, представляющей собой объединение асинхронного вызова функции и порождённого этим вызовом объекта Future.

1.2. Асинхронное программирование

Формирование асинхронных задач и выполнение асинхронных вызовов функций называют асинхронным программированием.

Итак — что такое асинхронное программирование? Его смысл в том, что особые длительные задачи можно запустить в фоне, отдельно от главного приложения. Вместо того чтобы блокировать весь остальной код приложения, заставляя его ждать завершения этих длительных задач, система получает возможность выполнять другие действия, не зависящие от этих задач. Затем, после завершения длительной задачи, мы получим уведомление об этом и сможем обработать результат.

Python Concurrency with asyncio, 2022, с. 3

Асинхронное программирование: использование особых приёмов асинхронного выполнения кода — таких, как выполнение асинхронных задач или вызовов функций.

Приёмы асинхронного программирования используются, в основном, в системах, предусматривающих применение неблокирующих операций ввода/вывода. Это, например, чтение данных из сокета или их запись в сокет при работе с другими процессами или системами.

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

Python Concurrency with asyncio, 2022, с. 18

Неблокирующий ввод/вывод — это когда программа запрашивает у системы чтение или запись неких данных, а соответствующие операции выполняются асинхронно. Вызывающей стороне не нужно ждать завершения операции перед переходом к другим задачам.

Операции чтения и записи выполняются тем или иным образом (например — операционной системой, или некими системами, построенными на её основе), а сведения о состоянии операций и/или данные, полученные в ходе их выполнения, вызывающая сторона получает позднее, как только они будут готовы, или когда вызывающая сторона готова будет их принять.

Неблокирующая обработка ввода/вывода: выполнение операций ввода/вывода посредством асинхронных запросов и ответов, а не таким образом, который предусматривает ожидание завершения операции.

Таким образом — можно видеть, как неблокирующий ввод/вывод связан с асинхронным программированием. На самом деле, мы либо используем неблокирующий ввод/вывод через механизмы асинхронного программирования, либо неблокирующий ввод/вывод реализован посредством таких механизмов.

Комбинация неблокирующего ввода/вывода с асинхронным программированием так распространена, что её обычно называют асинхронным вводом/выводом.

Асинхронный ввод/вывод: условное сокращение, которое означает комбинацию асинхронного программирование и неблокирующей обработки ввода/вывода.

Теперь давайте разберёмся с асинхронным программированием в Python.

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

В широком смысле асинхронное программирование в Python — это выполнение запросов без блокировки основной программы ради ожидания их завершения.

Асинхронные механизмы в Python можно реализовать разными способами, но при этом некоторые из них лучше других соответствуют модели конкурентности Python.

Первый и наиболее очевидный подход к разработке асинхронных Python-программ заключается в применении модуля asyncio. Этот модуль напрямую предлагает программисту среду для асинхронного программирования, в которой используется синтаксическая конструкция async/await и неблокирующий ввод/вывод с применением сокетов и подпроцессов.

Название модуля asyncio — это сокращение от asynchronous I/O (асинхронный ввод/вывод). Это — Python-библиотека, которая позволяет нам выполнять код, используя модель асинхронного программирования. Такой подход даёт возможность одновременно обрабатывать несколько операций ввода/вывода, а приложение при этом не теряет возможности реагировать на внешние воздействия.

Python Concurrency with asyncio, 2022, с. 3

Всё это реализовано с использованием корутин. Они выполняются в цикле событий, а сам он выполняется в единственном потоке.

Asyncio: асинхронная среда программирования, представленная в Python благодаря модулю asyncio.

Если говорить о других подобных механизмах — то в Python можно работать с потоками и процессами, которые способны асинхронно выполнять задачи.

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

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

The Art of Multiprocessor Programming, 2021, с. 76

А точнее — Python даёт в наше распоряжение пулы потоков и процессов, основанные на концепции исполнителей. Реализовано это, соответственно, посредством классов ThreadPoolExecutor и ProcessPoolExecutor.

Эти классы используют одинаковый интерфейс и поддерживают асинхронные задачи с помощью метода submit(), который возвращает объект Future.

Модуль concurrent.futures предоставляет высокоуровневый интерфейс для асинхронного выполнения вызываемых объектов. Асинхронное выполнение объектов может быть произведено с помощью потоков — посредством ThreadPoolExecutor, или с помощью отдельных процессов — посредством ProcessPoolExecutor.

concurrent.futures — Launching parallel tasks

Модуль multiprocessing тоже предоставляет пулы воркеров, использующие процессы и потоки, посредством классов Pool и ThreadPool. Это — предшественники классов ThreadPoolExecutor и ProcessPoolExecutor.

Возможности этих классов описываются в терминах воркеров, асинхронно выполняющих задачи. Они явным образом предоставляют синхронные (блокирующие) и асинхронные (неблокирующие) версии каждого метода для выполнения задач.

Например, можно запустить функцию, предназначенную для особого случая, либо сделав это синхронно, воспользовавшись методом apply(), либо асинхронно — с помощью метода apply_async().

Объект пула процессов управляет пулом процессов-воркеров, которым можно отправлять задания. Он поддерживает асинхронную выдачу результатов с использованием тайм-аутов или коллбэков и имеет параллельную реализацию метода map ().

multiprocessing — Process-based parallelism

Существует один аспект асинхронного Python-программирования, который менее жёстко связан с реализацией конкурентности в Python.

Например, Python-процессы асинхронно принимают или обрабатывают сигналы. Сигналы — это, по своей сути, асинхронные события, отправляемые из одних процессов другим процессам.

Этот механизм, в основном, поддерживается благодаря модулю signal.

Теперь, когда мы разобрались с тем, что такое асинхронное программирование, поближе присмотримся к asyncio.

2. Что такое asyncio

В Python понятие «asyncio», в широком смысле, означает возможность реализации механизмов асинхронного программирования с использованием корутин.

Если говорить конкретнее, то понятие «asyncio» имеет отношение к двум элементам:

  • Добавление модуля asyncio в стандартную библиотеку Python в Python 3.4.

  • Добавление выражений async/await в языковой арсенал Python в Python 3.5.

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

В Python 3.4 появилась библиотека asyncio, а в Python 3.5 — ключевые слова async и await, позволяющие удобно работать с этой библиотекой. Эти новшества языка открыли дорогу так называемому «асинхронному программированию».

Using Asyncio in Python, 2020, с. VII

Рассмотрим подробнее эти два аспекта asyncio, начав с изменений, внесённых в язык.

2.1. Изменения, внесённые в Python для добавления в язык поддержки корутин

Языковые возможности Python изменились для того, чтобы приспособить язык к работе с модулем asyncio. Сделано это посредством добавления в Python новых выражений и типов данных.

Точнее — язык изменился ради обеспечения стандартной поддержки корутин. А корутины, в свою очередь, являются компонентом системы конкурентного выполнения кода. Этот компонент используется в программах, в которых применяется модуль asyncio.

Корутина — это функция, выполнение которой можно приостановить и возобновить.

Корутина: корутины — это более общая форма подпрограмм. Подпрограммы имеют одну точку входа и одну точку выхода. А корутины поддерживают множество точек входа, выхода и возобновления их выполнения.

Python glossary

Корутину можно определить с использованием выражения async def. Она может принимать аргументы и возвращать значение — так же, как и функция.

Например:

# определение корутины
async def custom_coro():
    # ...

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

...
создание объекта корутины
coro = custom_coro()

Корутина может запустить другую корутину посредством выражения await.

Это выражение приостанавливает выполнение вызывающей стороны и планирует выполнение целевого объекта.

...
приостановить выполнение кода и запланировать выполнение целевого объекта
await custom_coro()

Асинхронный итератор — это итератор, который выдаёт объекты, допускающие ожидание.

Асинхронный итератор: объект, который реализует методы aiter() и anext(). Метод anext() должен возвращать объект, допускающий ожидание. Конструкция async for разрешает объекты, допускающие ожидание, возвращённые методом anext() асинхронного итератора до тех пор, пока он не вызовет исключение StopAsyncIteration.

Python glossary

Асинхронный итератор можно обойти, используя выражение async for.

...
обход асинхронного итератора
async for item in async_iterator:
    print(item)

Применение этой конструкции не приводит к параллельному выполнению цикла for.

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

Асинхронный менеджер контекста — это менеджер контекста, который может приостанавливать выполнение в своих методах enter и exit.

Asynchronous Context Managers and «async with»

Выражение async with используется для создания и использования асинхронных менеджеров контекста.

Вызывающая корутина приостановится и подождёт менеджер контекста до входа в блок менеджера и похожим образом поступит при выходе из блока менеджера контекста.

Это — сводка основных изменений, внесённых в Python для поддержки корутин.

Теперь поговорим о модуле asyncio.

2.2. Модуль asyncio

Модуль asyncio даёт нам функции и объекты для разработки программ, основанных на корутинах с использованием парадигмы асинхронного программирования.

В частности, этот модуль поддерживает неблокирующую обработку ввода/вывода с использованием подпроцессов (для выполнения команд) и с использованием потоков (для программирования TCP-сокетов).

Asyncio — это библиотека для написания конкурентного кода с использованием синтаксических конструкций async/await.

asyncio — Asynchronous I/O

Центральной концепцией модуля asyncio является цикл событий.

Это — механизм, который выполняет программы, основанные на корутинах, и реализует кооперативную многозадачность корутин.

Цикл событий — это база любого asyncio-приложения. Цикл событий выполняет асинхронные задачи и коллбэки, сетевые операции ввода/вывода, подпроцессы.

Event Loop

Этот модуль предоставляет и высокоуровневый, и низкоуровневый API.

Высокоуровневый API предназначен для разработчиков Python-приложений. А низкоуровневый API — преимущественно для разработчиков фреймворков, но, в большинстве случаев, не для разработчиков приложений.

Большинство вариантов использования asyncio реализуются с использование высокоуровневого API, который даёт в распоряжение программистов инструменты для работы с корутинами, потоками, примитивами синхронизации, подпроцессами и очередями, предназначенными для разделения данных между корутинами.

Низкоуровневый API — это основа высокоуровневого API, в его состав входят внутренние механизмы цикла событий, транспортные протоколы, политики и другие механизмы.

…тут имеются низкоуровневые API для разработчиков библиотек и фреймворков.

asyncio — Asynchronous I/O

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

Далее — поговорим о том, что нужно принимать во внимание при использовании asyncio в своих Python-программах.

О, а приходите к нам работать?

© Habrahabr.ru