Введение
Традиционные модели программирования, основанные на синхронной работе кода, не всегда отвечают требованиям скорости, принятым в современной разработке программного обеспечения. На помощь приходит концепция асинхронного программирования. Это качественно новый подход, который экономит ресурсы и повышает производительность работы программиста. Он оказывается особенно важен для обработки больших датасетов, сетевых операций, операций ввода/вывода данных.
Разберемся в основных понятиях и особенностях асинхронного программирования на языке Python, расскажем о его достоинствах, важных нюансах и неочевидных сложностях (которые, впрочем, можно преодолеть, если есть желание и умение).
В чем разница между синхронным и асинхронным программированием?
Сравним между собой две концепции программирования.
Синхронное программирование | Асинхронное программирование |
Все действия выполняются строка за строкой, в установленном порядке.Ни одна операция не может начаться, пока не завершилась предыдущая.Если какая-либо операция блокирует выполнение кода, он останавливается. | Возможно параллельное выполнение задач кода с переключением между ними.Если какая-то задача ждет завершения, другие могут выполняться параллельно.Ресурсы расходуются более экономно: не приходится ждать завершения каждой операции. |
Приведем примеры с загрузкой файлов и обработкой данных:
- синхронный код — программа ждет загрузки файлов и только по завершении загрузки последнего из них начинает обработку.
- асинхронный код — обработка данных происходит одновременно с продолжением загрузки.
Асинхронное программирование получило популярность с появлением масштабных приложений и высоконагруженных экосистем. В результате серверные платформы работают без зависаний, контент в мобильных приложениях загружается быстрее, а лента новостей в новостном виджете регулярно обновляется. Даже если какая-то задача «тормозит», асинхронный код, лежащий в основе программы, помогает это обойти.
Концепции асинхронного программирования
Теперь разберем основные понятия асинхронного программирования в Python.
Корутины
Это особые функции, способные сначала остановить свое выполнение, а потом возобновить его после перерыва. В Python они реализуются при помощи ключевого слова async и функции await.
Пример — скачивание файла из интернета. Загрузка приостанавливается в ожидании ответа сервера, затем возобновляется.
Асинхронные генераторы
Это генераторы, способные делать перерывы во время работы с помощью функций yield и async. Они могут быстро обрабатывать последовательности данных, не мешая всей программе работать.
Пример — генератор, способный читать и обрабатывать данные с сервера, выдавая результат обработки.
Асинхронные менеджеры контекста
Это специальные помощники, отвечающие за то, чтобы ресурсы асинхронных функций всегда были в порядке. Они нужны для освобождения ресурсов, работа с которыми завершена: например, сетевых сокетов или соединений с базой данных.
Менеджеры контекста важны для асинхронных функций: они помогают избежать утечек ресурсов, повышая безопасность программ.
Практическая реализация асинхронного программирования в Python
Разберемся, как реализованы принципы асинхронного программирования в языке Python.
Библиотека asyncio
Это стандартная библиотека Python для реализации алгоритмов асинхронного программирования. В нее входят инструменты для работы с корутинами, менеджерами контекста, асинхронными генераторами.
В основе работы asyncio лежит цикл событий, способный переключать контекст. Библиотека запускает только одну корутину параллельно основной, а переключаться может только в точках, определенных программистом. В итоге программный код не подвергается гонке потоков.
Иногда в работе asyncio возможны ситуации, когда две корутины вызывают друг друга, что приводит к взаимной блокировке. Однако на практике такие ситуации крайне редки и не свойственны большинству стандартных проектов на Python.
Так как корутины запускаются параллельно в одном потоке, не занимая дополнительной памяти, ресурсы расходуются экономнее, чем при последовательном выполнении задач. В то же время в asyncio существует свой пул потоков — так называемых «исполнителей» (executors). Если запустить в нем параллельно слишком много процессов, есть риск чрезмерного расхода и последующей нехватки ресурсов.
В целом, asyncio — относительно новая библиотека, добавленная в Python сравнительно недавно. Некоторые недоработки еще не исправлены, но пока это самый удобный способ для работы с асинхронным кодом на Python.
Один из примеров использования asyncio — асинхронное скачивание файлов. Вот фрагмент кода.
import asyncio
import aiohttp
async def download_file(url, filename):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
with open(filename, 'wb') as f:
f.write(await response.read())
async def main():
await asyncio.gather(
download_file('https://example.com/file1.txt', 'file1.txt'),
download_file('https://example.com/file2.txt', 'file2.txt')
)
asyncio.run(main())
Другой пример — асинхронная обработка данных:
import asyncio
async def process_data(data):
# Обработка данных
await asyncio.sleep(1) # Имитация работы
return data * 2
async def main():
tasks = [process_data(i) for i in range(10)]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
Запросы и вызовы функций в asyncio происходят в фоновом режиме, и поэтому результаты ее работы видны не сразу. Зато другие задачи можно выполнять параллельно, а результаты функций конкретной библиотеки обработать уже позже.
Преимущества асинхронного программирования
Возможность переключаться между задачами во время выполнения кода дает разработчику много преимуществ.
Повышение производительности. Вместо того, чтобы ждать завершения задачи, программа автоматически переключается на другую и работает значительно быстрее.
Возможность выполнять несколько задач одновременно без перегрузки системы. Если синхронное программирование можно сравнить с производственной линией, управляемой одним оператором, то асинхронное — с работой цеха, где каждый отвечает за свою задачу, при этом все могут работать одновременно.
Значительная экономия ресурсов. Асинхронные программы не ждут, пока блокирующая операция завершит работу. Вместо этого сервис переключается на решение других задач, экономя время.
Сложности асинхронного программирования
Достоинства асинхронного программирования очевидны, но в нем есть и свои сложности. Обозначим их.
Сложность отладки. Асинхронные программы могут быть сложнее в отладке, так как код выполняется нелинейно, непоследовательно, как будто «перепрыгивая» с одной задачи на другую. Отладка таких программ похожа на решение головоломок с множеством вводных.
Необходимость изучения новых концепций. Проще говоря, асинхронному программированию надо учиться, особенно начинающим. Корутины, асинхронные генераторы, менеджеры контекста — все это придется изучать, а лишь потом применять на практике.
Совместимость с синхронным кодом. Время от времени даже в асинхронной среде приходится использовать синхронный код. Результат — проблемы с производительностью, когда результаты работы разных фрагментов не совпадают.
Чтобы эффективно использовать асинхронный код, нужны знания и опыт, а они приходят только с практикой. У начинающих разработчиков не всегда хватает профессионализма для реализации таких проектов, но со временем всему можно научиться.
Заключение
Благодаря асинхронному программированию рутинные задачи выполняются гораздо быстрее, потому что программа переключается с одной операции на другую и обрабатывает больше запросов, параллельно экономя ресурсы всей системы. Но чтобы избежать проблем с производительностей, трудностей с отладкой, совмещением синхронных и асинхронных фрагментов кода, стоит помнить несколько важных принципов.
1. Планируйте свой код. В условиях постоянного переключения между задачами он все равно должен работать корректно.
2. Пользуйтесь инструментами отладки для асинхронного кода. В asyncio для этого существует специальный режим отладки, вызываемый несколькими способами:
- установка переменной среды PYTHONASYNCIODEBUG=1.
- запуск скрипта с параметром -X dev в командной строке.
- использование аргумента debug=True в функции asyncio.run().
- вызов метода loop.set_debug().
3. Осваивайте библиотеки и концепции для написания асинхронного кода, набирайтесь опыта.
Если постоянно практиковаться и помнить о сложностях подхода, то умение работать с асинхронным кодом становится большим плюсом с точки зрения профессионализма разработчика.