Блокировка event loop довольно серьезная проблема в асинхронных приложениях, потому что приводит к замедлению не конкретного запроса пользователя, а сразу всех запросов пользователей. Так давайте разберемся, что же это значит и, насколько это страшно и для разработчика и для клиентов сервиса.
Как происходит блокировка?
Работая в Python мы всегда используем процесс и поток, даже когда работаем над простейшими программами типа Hello World!. В главном потоке главного процесса запускается Python код. В момент вызова блокирующих функций программа приостанавливает свою работу, главный поток блокируется до тех пор, пока функция не завершит свою работу. И программа дальше продолжает исполнение инструкций.
Какие функции блокируют поток?
Ответ достаточно прост, все функции блокируют поток.
Обычные функции типа:
print("Hey!")
.GET-запрос к ресурсу с помощью библиотеки requests.
Сериализация/диссериализация данных с помощью пакета json.
Если всё блокирует поток, то почему asyncio не блокирует?
Суть этого явления находится в самом «сердце» асинхронности, а именно в цикле событий. Давайте представим себе небольшой круговой конвейер, который только запустили (момент старта приложения, создания и запуска нового цикла событий) и работника (главный поток), который стоит в центре этого конвейера. Задача конвейера и работника - выполнение работы по сборке и упаковке товаров.
У работника всего две цели:
Когда перед ним возникает разобранная картонная коробка, он должен её собрать.
Когда перед ним возникает укомплектованная товаром коробка, он должен её запаковать.

Теперь представим как происходит процесс упаковки товаров:
Когда поступает заказ (создается корутина), на конвейер сверху падает разобранная коробка (создаётся задача и она поступает в цикл события).
Она подъезжает к работнику и он её собирает (главный поток выполняет задачу, в которой происходит сетевой вызов удалённого ресурса).
Коробка едет дальше по конвейеру на укомплектование товаром (ждем пока не вернется ответ от ресурса).
В это время к работнику подъезжает новая разобранная коробка, которую он тоже собирает.
Вдруг перед работником появляется самая первая коробка, уже укомплектованная товаром (ответ от сервиса был получен), он её запаковывает (отдаёт ответ программе).
Цикл продолжается до тех пор, пока конвейер не остановится (остановка цикла событий) или пока задачи на упаковку не кончатся (задачи в цикле события).
Так как поток не ждет (блокируется) пока ответ на запрос вернется, а может отправить ещё несколько запросов перед тем, как будет получен первый ответ. И только благодаря циклу событий мы можем эффективно использовать наши приложения, без блокировок. Этот вид IO называется не блокирующее IO.
Как тогда происходит блокировка цикла?
У asyncio есть специальный синтаксис(async/await), если вы используете функционал, который поддерживает его, значит вы можете не бояться блокировок. Есть исключения, некоторые библиотеки мимикрируют под асинхронные, а на самом деле внутри используют модуль threading, например aiofiles. Но тут встаёт вопрос, а все ли функции в Python его поддерживают? Краткий ответ - нет. Блокировка потока произойдёт в тот момент, когда вам необходимо десcериализовать (спарсить JSON) ответ от ресурса. Это уже не IO операция, CPU операция. Данная операция требует вычислительной мощности процессора, которая заблокирует вашу программу на определенное количество времени, пока не закончится диссериализация.
Чем грозит блокировка цикла?
Представим ситуацию: у вас 100 тыс. клиентов, которые отправляют вам 100 тыс. запросов единовременно. Вы посчитали примерную скорость ответа (0.1-0.3 секунды). Как мы уже знаем скорость получения всех ответов равна скорости самого медленного ответа, то есть всем 100 тыс. клиентам мы отдадим ответ максимум за 0.3 секунды. Это довольно неплохой результат.
Далее представим, что каждый запрос требует примерно 1 миллисекунды процессорного времени:
100 000 0.001 = 100 секунд
Тогда ответ всем клиентам займет:
100 секунд + 0.3 секунды = 100.3 секунды = 1 минуте 40 секунд 3 миллисекунды.
То есть ваше приложение будет существенно тормозить с ответами при наличии CPU операции. Данный расчёт не включает многие моменты, он является теоритическим, только для демонстрации.
Является ли это проблемой?
Все зависит от конкретной ситуации.
Да, это проблема если у вас высокий RPS или очень тяжелая CPU операция.
Нет, это не проблема если у вас низкий RPS и относительно быстрая CPU операция.
Что с этим можно сделать?
Если для вас критична скорость отклика приложения или у вас подтормаживает приложение,
то вам стоит вынести тяжелую CPU операцию в другой сервис, а возможно и в соседний Python процесс, чтобы не мешать вашему циклу событий.
Если вам понравилась статья, вы можете посмотреть маленькие мини-рубрики в моем Telegram канале.
SeleznevAS-dev
Не хватает оперирования терминами, например, GIL (Global Interpreter Lock), также было бы здорово увидеть наглядные примеры кода, потому что есть конкретный язык, есть конкретная библиотека, но нет конкретных примеров.