«Самое худшее, когда нужно ждать и не можешь ничего сделать. От этого можно сойти с ума»
Э.М. Ремарк
Привет! Меня зовут Артём, я backend-разработчик в KTS. Я занимаюсь проектами, где повсеместно используется асинхронное программирование, и веду курсы по нему в нашей школе Metaclass.
Сегодня я постараюсь объяснить, что такое асинхронное программирование, зачем оно нужно, какие задачи решает и как ему научиться. Так как мой основной язык — Python, то и материал будет Python-ориентированным.
Что будет в статье:
Поехали! ????
Чтобы более наглядно разобраться в материале, нам вызвались помогать две подружки: Сима и Ася. Сима пропагандирует Синхронный образ жизни, а Ася предпочитает Асинхронный. Я постараюсь иллюстрировать различия между асинхронным и синхронным подходами бытовыми примерами с их участием, а затем приводить реальный кейс из мира разработки.
Что такое асинхронное программирование
Начнем с бытового примера
Сима и Ася устроили соревнование — кто быстрее приготовит жаркое. Для жаркого нужно купить цыпленка и овощей в ближайшем магазине, где, к сожалению, нет самообслуживания.
???? Сначала в магазин пошла Сима. Она попросила продавца взвесить цыпленка и стала пристально следить за процессом взвешивания. Продавец взвешивал цыпленка одну минуту, а затем отдал ее Симе. После этого Сима пошла в другой отдел и также же пристально смотрела на продавца, которая взвешивал ей овощи тоже в течение одной минуты. В итоге в магазине Сима провела 2 минуты, не считая времени на кассе.
???? Ася же попросила взвесить цыпленка, а потом, не дожидаясь продавца, пошла в другой отдел и попросила взвесить овощей. Почти целую минуту Ася наблюдала, как плавают карпы в магазинном аквариуме, а потом ее окликнул продавец с мясом. Сразу же за этим ее окликнул продавец овощей. В итоге в магазине Ася провела всего 1 минуту и ещё полюбовалась на рыбок.
В мире разработки часто встречаются задачи, которые требуют для своего выполнения информацию от внешних ресурсов. В примере выше, информация — это картошка и курица.
Теперь перейдем к примерам из разработки
Вы создали сайт, который показывает прогноз погоды на завтра. Естественно, что у вас нет своей метеостанции во дворе и, чтобы узнать температуру и скорость ветра, вам необходимо сделать запрос к какому-то сервису, который эти данные может предоставить.
Как же разные подходы будут отправлять этот запрос и обрабатывать ответ?
Посмотрите на иллюстрацию выше.
???? Слева изображён синхронный подход. Синхронная программа отправляет запрос. Пока она ожидает результатов выполнения этого запроса, она не может выполнять другую полезную работу. Программа сможет продолжить работу только тогда, когда получит ответ от внешнего сервера, так как дальнейший ход ее действий зависит от внешних данных.
???? Справа изображен асинхронный подход. Асинхронная программа сначала ведет себя также как и синхронная — она посылает запрос на сервер. Но затем ее поведение кардинально отличается — она не блокируется, ожидая результатов, а делает какие-то другие действия, которые не требуют ответа внешнего сервера. Только после того, как сервером будет возвращён ответ, она сможет вернуться к дальнейшей обработке полученных данных.
Асинхронное программирование нацелено на то, чтобы программа как можно меньше времени бездействовала в ожидании какого-то внешнего события, другими словами, основная проблема синхронного программирования — «бездействие» во время ожидания.
Другие варианты решения проблемы блокировки во время ожидания
Конечно, асинхронное программирование — не единственный подход, при котором можно не блокироваться на время ожидания ответа. Начнем опять с бытового примера.
Представим, что в предыдущем примере в магазин отправились сестры-близняшки Сима_1 и Сима_2, одна из которых пошла в овощной, а другая в мясной отдел. Общее время их пребывания в магазине будет такое же как и у Аси — одна минута. Но на существование второй Симы нужны ресурсы — ей надо как минимум где-нибудь жить и чем-то питаться, чтобы ходить в магазины.
Немного утрируя, можно сказать, что Сима_1 и Сима_2 используют параллелизм для похода в магазин.
Параллелизм — подход, при котором код запускается в новом процессе или потоке компьютера, то есть несколько операций выполняются параллельно и не могут быть прерваны на выполнения каких-то других задач. В данном случае мы говорим именно о синхронной реализации программы внутри таких параллельных потоков/процессов.
Возможно, это объяснение покажется несколько сложным и неполным, но главная идея должна быть ясна: чтобы применять эти подходы на практике, надо создавать дополнительную сущность (процесс или поток). Дополнительные сущности требуют лишние ресурсы на свое поддержание, хотя в целом выполняют задачу за то же время, что и асинхронный подход. Конечно, было бы неправильно говорить, что параллелизм неэффективен, но вот в чём именно он эффективен, мы обсудим ниже.
Важно разделять понятия однопоточный и многопоточный с понятиями синхронный и асинхронный. Существуют все комбинации этих понятий, например можно написать многопоточную асинхронную программу, то есть вообразить ситуацию, когда в магазин пошли две Аси, а не две Симы. В этой статье я рассказываю про однопоточный асинхронный подход, основанный на принципе кооперативной многозадачности, так как именно этот принцип используется в asyncio и учитывает ограничения GIL. Таким образом: Ася - однопоточный асинхронный подход, Сима - однопоточный синхронный подход.
В каких задачах полезно асинхронное программирование
Задачи, в которых эффективен асинхронный подход, называются IO-bound задачи, где IO значит Input/Output (Ввод/Вывод), а bound переводится как ограничение. То есть задачи, время выполнения которых в основном регулируется временем выполнения всех операций ввода/вывода. Операции ввода/вывода — это обычно работа с сетью и файловой системой. Важно понимать, что асинхронный подход оптимизирует не время выполнения этих операций по-отдельности, а общее время работы программы, которая обычно состоит из множества таких операций.
В мире веб-программирования особенно много операций, в которых есть элемент ожидания. Давайте рассмотрим некоторые задачи, которые эффективно решать с помощью асинхронного подхода.
Пример 1. Создание файлового хранилища
Допустим, что вам поручили создать хостинг файлов — аналог Google-диска. Пользователь загружает файл, а сервис отдаёт ему ссылку, по которому этот файл можно скачать. Пусть сервис сохраняет файл на жёсткий диск, а связку (путь до файла):(ссылка, по которой он доступен) вставляет в базу данных.
Мы видим в таком задании сразу две очевидные IO-bound операции: запрос к внешней базе данных и запись файла на диск. Кроме этого, в задаче есть несколько неочевидных IO-bound операций — получение файла от пользователя полностью (это происходит по кусочкам) и раздача файла при его скачивании пользователем (тоже по кусочкам). Эти кусочки также называются чанками и их длина обусловлена протоколом передачи данных в Интернете.
???? В однопоточном синхронном подходе сервис будет обрабатывать только один запрос, т.е. одного пользователя, в момент времени. Синхронному сервису предстоит сначала дождаться получения файла на сетевую карту, затем его сохранения в файловую систему, ответа от базы данных, а затем передачи пользователю сгенерированной ссылки на скачивание.
???? В асинхронном подходе сервис сможет обрабатывать сразу много запросов конкурентно, постоянно переключаясь между ними при наличии новых данных.
Например, если от пользователя пришёл кусочек файла — сервис сохранит его на жёсткий диск, пришёл ответ от базы данных — отправит пользователю ссылку на файл и так далее.
А что, если придёт пользователь, который захочет загрузить фильм в качестве 4k, размером 40 GB?
Сначала бытовой пример
Представим, что Ася и Сима хотят сделать себе кофе с помощью новой кофемашины. Они не знают как ей пользоваться и поэтому читают инструкцию, где алгоритм описан по пунктам.
???? Сима сначала прочитала всю инструкцию, полностью запомнила ее, а затем начала делать кофе.
???? Ася выбрала другой подход: она читает один пункт инструкции, затем выполняет его и сразу же забывает.
В итоге Ася хранит меньше информации (только один пункт инструкции) и в любой момент ожидания может переключиться на другую задачу, а потом продолжить варить себе кофе.
???? Однопоточный синхронный сервис столкнётся сразу с двумя проблемами. Во-первых, пока он не обработает этого пользователя полностью, сервис будет недоступен для остальных. Есть ещё одна неочевидная проблема: при использовании стандартных подходов предстоит сначала дождаться полной загрузки файла в оперативную память сервера (то есть, в случае Симы, прочитать и запомнить всю инструкцию) и только после этого начать его обработку. Даже в наше время сервер с 40 GB RAM — достаточно дорогое удовольствие.
???? Асинхронный сервис обладает возможностью стримить файл сразу в хранилище по кускам (чанкам). То есть при получении нового чанка данных нам нет смысла хранить его у себя, если конечная цель — это запись на диск. Мы можем сразу же сохранить его на диск и очистить память. То есть размер памяти нашей программы всегда остаётся более-менее одинаковым вне зависимости от размера получаемых файла. Кроме этого, появляется возможность асинхронно обрабатывать каждый кусочек (то есть, прерываться после выполнения очередного пункта инструкции, если проводить аналогию с бытовым примером). Например, можно сообщать о текущем прогрессе загрузки файла через websocket-канал.
Вот пример наивной реализации такого подхода к скачиванию файлов с использованию библиотеки aiohttp.
Пример 2: Работа чат-бота
Некоторые технологии вообще не предполагают использования чисто синхронного подхода. Например, нам нужен чат-бот, который получает сообщение и отправляет на него ответное.
Бытовой пример
Вспомним пример с магазином. Представим, что курицу только разгружают на склад и продавец не может ее взвесить, пока не увидит, что грузчики закончили свою работу.
???? У Симы два варианта:
спросить продавца один раз и долго стоять, ожидая пока ей отдадут курицу, то есть завершится процесс разгрузки;
постоянно спрашивать, сильно раздражая продавца и срывая свой голос, можно ли ей наконец-то получить курицу;
В первом случае Сима потратит время, а во втором — нервы и силы.
В первом случае Сима использовала Long Polling подход, а во втором просто Polling. Давайте разберемся, что это за подходы и как они связаны с чат-ботами.
Long Polling — это метод, при котором мы посылаем «долгий» НТТР-запрос к серверу соцсети и получаем новые сообщения в ответе на этот запрос. «Долгий» означает, что соцсеть вернёт нам ответ только в двух случаях: когда произойдет новое событие или истечет время ожидания, т.е. мы долгое время ждём ответа. Во многих соцсетях мы можем получить сообщения с помощью этого варианта, например, в VK и в Telegram и именно этот подход мы используем в своем конструкторе ботов Smartbot Pro.
Long Polling неэффективен при однопоточном синхронном подходе, потому что программа будет просто заблокирована на время ожидания ответа от сервера (а это обычно 30-60 секунд, если новых событий нет). Если у вас только один бот, это решение имеет право на жизнь. Но если вы хотите обрабатывать сразу двух ботов, сначала вам надо завершить запрос к первому боту, затем обработать полученного от него сообщения, затем завершить запрос ко второму боту и так далее… В итоге, в худшем случае, пользователь напишет свое сообщение, как только закончится запрос к его боту, и будет ждать 30-60 секунд, прежде чем получить ответ.
Мы можем использовать Polling и отвечать значительно быстрее, постоянно спрашивая сервер: «А есть новые сообщения?». Сервер будет отвечать моментально, что «Нет, новых сообщений нет» или «Да, вот новые сообщения», но в этом случае мы каждый раз будем тратить время на установку соединения и делать много «бесполезных» запросов.
В каких задачах бесполезно асинхронное программирование
Рассмотрим бытовой пример:
Компания, в которой работают Сима и Ася, на Новый год подарила им пазлы.
???? Сима взяла пазлы и начала методично собирать их, не отвлекаясь ни на что другое. В итоге сбор пазлов занял один час.
Все знали, что Сима не любит отвлекаться, поэтому никто на нее не обиделся за то, что она этот час не отвечала в чатике.
???? Ася тоже взяла пазлы и начала их собирать, но из-за её немного непоседливого характера ей потребовалось чуть больше самообладания, чтобы собрать их за тот же один час. При этом люди, привыкшие, что она быстро отвечает в чате, обиделись на неё за игнор.
Обе девушки собрали пазлы за 1 час, но у Симы этот процесс был чуть более эффективен. Такие задачи, как сбор пазлов, в мире программирования можно назвать CPU-bound операциями.
???? CPU Bound операции — вычислительные задачи, которые полностью утилизируют вычислительные ресурсы, то есть эти задачи ограничены быстродействием процессора, а не ожиданием внешних ресурсов.
Можно ли выполнять такие задачи в сервисе, созданном в асинхронной парадигме? Да, можно, но это будет не совсем правильно.
Во-первых, асинхронный подход требует компонента-планировщика, который будет продолжать выполнение ожидающего кода при наступлении внешних событий. Обычно он называется событийный цикл или event loop. В CPU-bound операциях этот компонент бесполезен, но при этом всё равно тратит время на свои создание и работу.
Во-вторых, асинхронный сервис обычно пишется для взаимодействия со множеством операций в один момент времени, поэтому некоторые пользователи могут «обидеться», что такой сервис не отвечает им, так как выполняет долгую синхронную операцию. Это частая ошибка новичков: выполнять долгие вычисления в момент обработки запроса, приводящая к тому, что сервис перестает отвечать на другие запросы и «делает вид», будто он завис.
Несколько примеров CPU-bound операций: сортировка больших объемов данных, обучение ML-модели, архивирование файлов и т.д. Именно в таких задачах полезен такой подход как параллелизм, то есть эффективна помощь Симы_2 или, другими словами, возможность распределенных вычислений параллельно на нескольких ядрах процессора или на многих процессорах.
Важно ли знать, как писать асинхронный код в наше время
Поддержка современных принципов разработки
Стоит начать издалека и посмотреть, как вообще развивался веб.
На заре веба все сервисы были монолитными: есть одна большая сущность, в которой реализована вся бизнес-логика сервиса. Со временем люди поняли, что монолитная архитектура не всегда является лучшим решением. Из-за нее страдает общая отказоустойчивость приложения, часто скорость разработки, и её невозможно оптимально масштабировать.
Сейчас многие проекты пишутся или переписываются с использованием принципов микросервисной архитектуры. Отличительно чертой такого подхода является огромное число перекрестных запросов от микросервиса к микросервису или от микросервиса к базе данных. А как мы знаем, запросы - это IO-bound операции и с ними отлично справляется асинхронный подход. Поэтому если вы создаете приложение с микросервисной архитектурой, скорее всего, вам нужно будет использовать асинхронный подход.
Развитие библиотек и фреймворков в сторону асинхронности
Лучшей иллюстрацией движения мира к асинхронному подходу, пожалуй, является Django. Еще совсем недавно это был полностью синхронный фреймворк. С 3-й версии добавилась поддержка асинхронности и сейчас она становится все более полной. То есть, даже если вы будете писать проекты на скорее всего самом распространенном Python-фреймворке, то вам надо будет знать принципы асинхронности.
Многие сервисы изначально были написаны в синхронной парадигме. Например, возьмем Instagram, который был написан именно на синхронной версии Django. Казалось бы, как он справлялся с большой нагрузкой? Все благодаря команде Instagram, которая еще задолго до становления Django асинхронной, переписала его ядро и добавила поддержку асинхронности.
Вообще сам факт того, что Python добавил в 2015 году в свою стандартную библиотеку библиотеку для работы с асинхронными операциями - asyncio, уже говорит о многом: команда разработчиков ядра Python понимает, что за этим будущее разработки. На самом деле — уже и настоящее. С каждым годом выпускается всё больше и больше фреймворков и библиотек для асинхронного программирования. Для «старых» синхронных модулей либо пишется аналог (например, asyntnt), либо в них добавляется асинхронная логика.
Если вы захотите в команду, которая делает большие и современные проекты, то знаний только синхронного подхода вам скорее всего будет недостаточно. Если вы хотите пойти в какой-то крупный проект, который будет держать большую нагрузку, то с большой долей вероятности вам нужно будет уметь писать асинхронный код.
Почему стоит писать асинхронный код именно на Python
Скорость и простота разработки
Многие слышали, что Python — это относительно медленный язык. Логично предположить, что стоит сразу изучать асинхронную парадигму на более эффективном языке, но всё не так однозначно. Рассмотрим Golang для примера и начнем с бытовой аналогии.
Ася решила заняться вязанием и пришла на мастер-класс. На этот же мастер-класс пришла целая семья по фамилии Голыгины.
???? Ася быстро научилась вязать, хотя вяжет пока медленно. Ей потребовалось всего одно занятие и учитель-новичок.
???? Голыгины решили вязать несколько вещей сразу, при этом ещё и помогая друг другу. Чтобы наладить такое тесное взаимодействие, им понадобился опытный учитель (занятия с которым дороже) и пять занятий, зато они научились вязать вещи очень быстро.
В итоге для обучения меньше всего ресурсов потребовалось Асе, и она достаточно производительна для того, чтобы вязать своим друзьям подарки. Голыгиным потребовалось в несколько раз больше времени и денег, чтобы наладить свою работу, но при этом они могут делать в 10 раз больше вещей, чем Ася за то же время.
После учебы выяснилось, что у Голыгиных просто нет столько друзей, чтобы нужно было вязать всей семьей одновременно. Поэтому работать на пределе своих возможностей они не будут и вряд ли начнут в ближайшем будущем. Кроме того, если Ася поймет, что она не успевает, она легко может попросить Голыгиных, и они помогут ей.
Разработка на Python аналогична примеру: она требует меньше времени и компетенций на создание работающего продукта. Асинхронный код на Python с помощью asyncio можно писать даже не зная о примитивах синхронизации: мьютексах, семафорах, атомиках и так далее, можно практически не думать и о race conditions. С помощью asyncio вы пишете код, который выглядит как синхронный. Конечно, чем больше погружение, тем больше появляется нюансов, но код для выполнения базовых задач писать очень легко. Во многом это благодаря GIL, который снимает с разработчика ответственность за конкурентный доступ к памяти.
В Go же наоборот: необходимо всегда следить, что общая память используется правильно, то есть иными словами обеспечивать потокобезопасность. Говоря словами из примера, каждый член семьи Голыгиных вяжет именно тот кусок, который должен и не пришивает третий рукав на свитер, не зная, что там уже есть два. Это замедляет разработку, а баги становятся более сложными для диагностики и исправления.
В любом случае, если скорость работы в каком-то месте программы становится очень важна, можно вынести эту часть в виде отдельного микросервиса, написанного на более производительном языке программирования, или написать ее на C и встроить в Python. Например, вот бенчмарки (там же есть сравнение с Go) сервера на UVLoop — цикла событий, написанного на C и встроенного в Python в виде библиотеки.
Популярность языка
Если посмотреть на индекс популярности языков TIOBE, основанный на поисковых запросах пользователей, количестве квалифицированных программистов этого языка, и других факторах, можно увидеть, что Python стоит на первом месте.
Популярность Python означает, что:
Для него больше обучающих материалов.
Если у вас есть специфическая задача, с большей вероятностью вы найдёте на неё ответ на Stack Overflow именно для Python.
Больше людей параллельно с вами учат Python и разбираются в нём. А значит, есть большое комьюнити, в котором можно задать вопрос, и оно даст квалифицированный ответ.
Больше вакансий и больше проектов. Например, сейчас в Москве открыты 5000 вакансий на Python разработчика и всего 600 на Go.
Итоги
Асинхронное программирование показывает свою эффективность только там, где преобладают IO-bound операции. В CPU-bound операциях асинхронный подход бесполезен и даже вреден.
Следует отличать понятия синхронности/асинхронности и однопоточности/многопоточности.
Асинхронная программа хороша тем, что она в одном потоке может обработать столько же IO-bound операций, что и многопоточная синхронная, при этом потребляя минимум ресурсов и в, большинстве случаев, за меньшее время.
Существуют технологии, которые не предполагают использование однопоточного синхронного подхода, например Long Polling или стриминг.
В сложных и современных проектах в большинстве случаев используется микросервисная архитектура, которая предполагает множество IO-bound операций, а значит и асинхронный подход.
На Python есть множество асинхронных модулей, которые покрывают все потребности для создания почти любого проекта.
Для ускорения конкретных частей программы можно создать отдельный микросервис на более производительном языке или написать свой биндинг прямо в Python на Cython.
Python самый популярный язык в мире на текущий момент.
С чего начать учить asyncio
Если вы учите Python и уже кое-что умеете, приходите на наш бесплатный курс по бэкенду, который стартует 6 февраля. Уже после регистрации вам будут доступны некоторые задачи, которые можно начинать решать:
???? Начинающий Backend-разработчик на Python
Асинхронное программирование вы можете начать учить на другом бесплатном курсе KTS. Он рассчитан именно на работу с асинхронным кодом на Python с использованием библиотек aiohttp. Начинать учиться можно в любое время.
Заходите посмотреть программу по ссылке:
Комментарии (11)
abetkin
26.01.2023 23:02+5Многопоточные приложения не копируют память, у них есть доступ к общей памяти!
Насчёт вебсокета - соглашусь, что возможность скорее теоретическая, чем практическая, я просто хотел сказать, что вебсокеты и блокирующий ввод-вывод друг друга не исключают.
Кстати, ты не написал, что бы делала Ася, если бы пришла за курицей - что она бы решила вопрос легко и непринуждённо :)
Насчёт django - да, это всего лишь интерфейс для совместимости.
Про ввод-вывод в голанг первое, что нужно понимать читателям - что он всегда асинхронный. Что даёт основания предположить, что го предназначался для серверных приложений. Потом уже всё остальное.
Всё равно, статья хорошая - мои статьи так не плюсуют
adbakulev Автор
27.01.2023 09:30+1Спасибо большое за оценку и за замечание про память в многопоточности!
Внес это предложение в момент финальных правок и, видимо, на автомате провел аналогию из процессов к потокам. Исправил)
IkaR49
26.01.2023 23:44+2Первые три раздела великолепны, хотя не все бытовые примеры удачны, как мне кажется. Четвертый раздел вроде и верные вещи говорит, но мне показался спорным. Последний раздел, это прям повод для холивара ;)
В любом случае - спасибо, будет куда неофитов отправлять на почитать!
alexhott
27.01.2023 07:39-1Сима прочитала инструкцию к кофеварке за 5 минут и пошла готовить кофе, по расчетам ей нужно на приготовление 1 минуту, а там Ася читает инструкцию и выполняет действие и дошла только до 5 пункта из 10.
В итоге Сима ждет Асю, или готовит быстро кофе и возвращает кофеварку в состояние 5 пункта инструкции.
ЗЫ: При рассказе про асинхронность и как там все хорошо нужно упомянуть слово "потокобезопасность".
adbakulev Автор
27.01.2023 10:00+1Доброе утро!
В статье я указал, что речь идет про асинхронный однопоточный подход, но вы правы, я не связал это явно с Симой и Асей, а без этого не понятно как используются ресурсы. Например, в примере с кофеваркой, речь идет про разные кофеварки и разные инструкции к ним и соответственно никто никого не ждет. Внес правку, надеюсь сейчас станет понятнее, что я хотел сказать.
Также согласен, что потокобезопасность - это очень важная тема, но во-первых она достаточно сложна для вводной статьи, на мой взгляд, а во вторых, так как статья в основном про Python, то GIL позволяет нам не погружаться глубоко в синхронизацию доступа к памяти. Постарался намекнуть, что такая особенность возникает и очень важна, в части статьи про сравнение с Go, но решил не раскрывать до конца. Сейчас добавил слово "потокобезопасность" в статью, чтобы интересующиеся могли как минимум загуглить термин)
Спасибо, что дали фидбек и подсветили неясные места!
mgis
27.01.2023 10:37Ого, оказывается я как Ася. Никогда не дочитываю ни один туториал до конца, всегда с первого пункта начинаю делать, попутно решая проблемы если возникают.
Вот так вот, ваша статья, способствовала самопознанию.
Да и в целом было полезно.
event1
27.01.2023 18:41Ася программирует на Ассемблере, а Сима на Си.
В каких задачах бесполезно асинхронное программирование
Ещё, если нужно гарантированная задержка. То есть, если Симе курица нужна строго не позже чем через 5 минут после того, как она появится у продавца. Очень скоропортящаяся курица.
abetkin
Привет, Артём. Я понимаю, что статья, в основном - для начинающих, но поскольку она очень точно попадает в тему моих последних статей, не могу не оставить комментарий.
Насчёт Симы и Аси - это нормально придумано, засчитывается.
Здесь, профессор, Вы что-то странное написали.
Про файловое хранилище и кофеварку - этот пример, наверно, нужно додумать. При блокирующем подходе тоже можно готовить кофе по инструкции пункт за пунктом, и файл загружать чанками тоже можно, загружать весь файл в память, конечно, необязательно. Стриминг часто используется на практике сервисами, использущими блокирующий ввод-вывод.
Насчёт чатбота: сюрприз, блокирующий сервис тоже может дождаться ответа по вебсокету. Например, я использую nginx unit, который сам управляет соединениями по вебсокету, моё же питоновское приложение может использовать блокирующий ввод-вывод. Long Polling вообще почти не используют теперь.
Насчёт развития фреймворков: ядро django никто не переписывал и не собирается. django использует блокирующий ввод-вывод при работе с базой.
Насчёт Голыгиных - я бы не включал эту часть. Учитывая, что статья - для начинающих, они точно только запутаются. Го - async-only, причём асинхронность там была с самого начала. Хоть он более "verbose" чем питон в принципе, приводить питон в пример голангу в смысле асинхронности, наверно, неправильно. Сравнивать производительность на каких-то бенчмарках тоже смешно. Одно важное отличие - что го не использует "функции другого цвета" для горутин.
adbakulev Автор
Добрый вечер и спасибо за комментарий! Как-раз читал вашу сегодняшнюю статью в момент финальных правок по своей)
Постараюсь обосновать по пунктам, почему я написал именно так:
1) Спасибо за одобрение Симы и Аси, надеюсь вышло наглядно)
2) Говоря о параллелизме я хотел донести, что это всегда про многопоточные приложения, то есть как раз про те которые копируют память полностью или частично при запуске параллельного вычисления. В контексте того параграфа, мне важнее было донести, что параллелизм требует накладных расходов на вспомогательные сущности (потоки/процессы), чем погружаться в полный разбор как параллелизм можно организовать.
3) В пункте о скачивании файлов по-чанково я действительно немного слукавил и поэтому специально пометил курсивом текст "стандартными средствами". Например, в том же Django (говорю про версию < 4) стандартным средством, по моему мнению, для обработки загружаемого файла является форма с полем forms.FileField и соответственно стандартные хендлеры загрузки, логику работы которых необходимо менять, чтобы появилась возможность обрабатывать файл по кусочкам на стороне view.
4) Насчет ответа по вебсокету - согласен, но я и не исключал этой возможности в синхронном подходе. Синхронный сервис в принципе может дождаться всего, чего захочет, но будет ли это ожидание эффективно? Скорее всего нет. Вы сами пишите, что используете прослойку для работы с вебсокет-соединениям.
Long polling можно не использовать, если у вас есть такая возможность. VK и Telegram, например, не позволяют установить websocket канал для получения событий, а функционал webhook'ов в ходе наши долгих наблюдений, оказался менее надежен и быстр, чем Long Polling (имею в виду именно доставку сообщений до нашего приложения).
5) Насчет Django - я сказал лишь то, что поддержка асинхронности последнее время становилась все более полной, но возможно, как вы сказали, этот процесс остановится. Про полностью блокирующий ORM - не совсем согласен. Я, скорее всего не углублялся в текущую ситуацию с ORM также как вы, но руководствуюсь информацией из официальной документации, где сказано, что частично это уже реализовано, но возможно что это лишь интерфейс для совместимости.
6) Про Голыгиных я написал специально, так как в свое время проводил вебинар по этому материалу и многим слушателям было интересно именно некое, пусть немного субъективное, сравнение с Golang и nodejs, так что "новички" в целом не должны поразиться сложности материала.
С nodejs я не стал проводить сравнение по причинам крайней похожести модели асинхронности, а вот с Go провел по двум причинам (при этом почти не обсуждая преимущества и недостатки реализации именно асинхронной модели).
Во-первых, обозначить минусы разработки на Go по отношению к Python и сделать акцент на то, что иногда важнее скорость разработки, а не скорость работы программы.
Во-вторых, развенчать некий миф, что приложения на Python - это всегда что-то медленное и не подающееся ускорению. Именно поэтому и прикрепил ссылку на бенчмарки "в вакууме" - не для того, чтобы показать, что Python лучше всех в идеальных условиях, а для того, чтобы продемонстрировать, что существуют инструменты (например, сишное API), которые позволяют программам на Python показывать схожую Go производительность, как минимум в искусственных задачах.
Надеюсь, что мне удалось, хотя бы в некоторых пунктах, объяснить вам почему я написал именно так, а не иначе, но если я не прав, то, пожалуйста, укажите на мои ошибки.