%

Попробуй
бесплатно

23:42:51

3 дня

%

  • Компьютерная академия
  • Школа
  • Колледж
  • ВУЗ
  • Английский
  • Не школа музыки
Москва

Гайд по асинхронности в Python

Ошибки цикла событий, путаница с потоками — типичные «головные боли» Python-разработчиков. Как работает асинхронность и зачем она нужна — рассказывает преподаватель Академии ТОП Михаил Шмаров

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

Это происходит почти со всеми, кто начинает разбираться с асинхронностью в Python. Наш гайд от Михаила Шмарова, преподавателя Академии ТОП, закроет все пробелы. Вы перестанете бояться async/await, не будете избегать асинхронных функций и начнете уверенно применять их там, где они реально дают прирост скорости.

                Михаил Шмаров
Михаил Шмаров

Python «медленный»: всегда ли причина в языке

Вы наверняка не раз слышали, как коллеги жалуются: «Python такой медленный, ничего с ним не сделаешь». Но на деле это не всегда вина языка или интерпретатора. Часто проблема в том, как код написан и какие задачи он решает.

Задачи в программировании делятся на два типа — CPU-bound (рус. «процессорозависимые») и I/O-bound (рус. («ограниченные вводом-выводом»), и от этого различия зависит, поможет ли асинхронность.

CPU-bound задачи — тяжелые вычисления, обработка больших массивов данных, математические симуляции, рендеринг изображений. Это все то, что «поглощает» ресурсы процессора. Здесь Python может быть медленнее, чем compiled языки вроде C++, из-за интерпретации, динамической типизации и Global Interpreter Lock, GIL, (рус. «глобальная блокировка интерпретатора»), который не дает полноценно использовать несколько ядер в потоках. Асинхронность здесь не поможет.

При выполнении I/O-bound задач код в основном ждет внешнего отклика: ответов от API, чтения/записи файлов, запросов к базе данных или даже пользовательского ввода. Здесь процессор почти не занят — он просто стоит на паузе, пока не придут данные по сети или с диска. 

Ускорить Python в таких задачах можно — за счет асинхронности.

Где Python реально ждет, а не считает

Везде, где есть блокирующие операции:

  • Сеть: HTTP-запросы, сокеты, API-запросы — время ожидания от 100 мс до секунд в зависимости от сервера.

  • Файлы и диск: чтение/запись больших файлов, особенно если диск медленный или фрагментирован.

  • Базы данных: SQL-запросы, где ожидание ответа от сервера баз данных может занять от миллисекунд до минут.

  • Внешние сервисы: интеграции с облаками, имейл-серверами или даже функция sleep() в тестах, который просто имитирует функцию wait().

В этих случаях «медлительность» — не вина Python. Код работает последовательно, хотя мог бы переключаться между задачами.

Нет времени читать статью?

Получите ответы от практикующих специалистов на бесплатном занятии в вашем городе

Нажимая на кнопку, я соглашаюсь на обработку персональных данных

Синхронный код: как он работает на самом деле

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

Когда ваш код доходит до блокирующей операции, весь поток останавливается и ждет, пока она завершится. Процессор в это время ничего не делает. Это и есть ключевая причина, почему многие скрипты и серверы на «обычном» Python кажутся медленными, хотя CPU загружен на 5–15 %.

Почему time.sleep() — враг производительности

Time.sleep() — это классический пример блокирующей функции. При ней интерпретатор передает управление операционной системе. ОС ставит ваш поток в состояние ожидания на указанное в скобках время. Например: time.sleep(3) блокирует весь поток на 3 секунды.

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

Асинхронность простыми словами

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

Ключевое здесь — «ожидание». Асинхронность не делает код быстрее и не выполняет несколько вычислений одновременно. Она позволяет эффективно использовать время, которое в синхронном коде просто теряется.

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

В Python роль кухни — это внешние операции: сеть, база данных, файловая система. Роль официанта — формирование цепи событий (event loop). Он не выполняет задачи параллельно, а управляет очередью задач и переключается между ними, когда одна из них уходит в ожидание.

Чтобы весь процесс шел так, как задумано, важно «не блокировать выполнение» — то есть не останавливать поток целиком на время ожидания. 

Event Loop: что это и кто им управляет

Event loop — это механизм выполнения асинхронного кода в Python, который управляет сопрограммами (корутинами), ожидающими операций ввода-вывода, и распределяет время выполнения между ними.

Простыми словами, это бесконечный цикл, который постоянно спрашивает: «Есть ли какая-то работа, которую можно выполнить прямо сейчас?». Он:

  • запускает корутины, которые готовы к выполнению;

  • приостанавливает корутины, которые встретили await и ждут чего-то (ответа от сети, таймера, файла и т. д.);

  • возвращается к корутинам, когда то, чего они ждали, готово;

  • выполняет функции обратного вызова (коллбэки), таймеры, обработку ввода-вывода и другие события.

Без event loop асинхронность в asyncio не работает

Почему event loop один

В рамках одного потока выполнения кода может работать только один event loop. Это ограничение зафиксировано в архитектуре asyncio и напрямую следует из модели исполнения Python.

Причины практические:

  • event loop управляет очередью задач и таймерами;

  • он должен быть единственной точкой принятия решений;

  • одновременное управление несколькими loop в одном потоке приводило бы к конфликтам состояния.

Именно поэтому порой возникает распространенная ошибка RuntimeError: no running event loop. Код пытается получить доступ к event loop там, где он еще не запущен или уже завершен. Это не баг, а сигнал, что асинхронный код вызван вне правильного контекста.

Await в основном модуле
Await в основном модуле

Кто управляет event loop

Event loop не запускается автоматически при импорте asyncio. Его жизненным циклом управляет внешний код. В современных версиях Python эту роль обычно выполняет:

  • asyncio.run() — в обычных приложениях и скриптах;

  • асинхронный фреймворк (например, FastAPI) — в веб-приложениях.

Работе с FastAPI посвящен отдельный модуль курса «Python-разработчик с нуля до PRO» в Академии ТОП.

Синтаксис async и await простыми словами

Async и await — это синтаксис, который позволяет работать с корутинами и управлять моментами, когда выполнение может быть приостановлено без блокировки event loop. 

Что делает async def

Async def объявляет корутину.

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

Корутина начнет выполняться только тогда, когда:

  • по ней вызовут await;

  • event loop запланирует ее выполнение как задачу.

Async def сам по себе ничего не делает асинхронным. Он лишь делает функцию совместимой с event loop.

Что происходит в момент await

Await — это точка кооперации с event loop.

Когда выполнение корутины доходит до await:

  • корутина приостанавливается;

  • управление возвращается к event loop;

  • event loop может продолжить выполнение других корутин;

  • когда ожидаемая операция завершена, корутина возобновляется с того же места.

С точки зрения логики программы это выглядит как обычное ожидание результата. С точки зрения модели выполнения — это добровольная пауза, которая позволяет не блокировать поток.

Await работает только с объектами, которые поддерживают протокол ожидания. Если объект не умеет отдавать управление event loop, await не сработает.

Почему нельзя просто «вставить await»

Причина простая: await не делает код асинхронным автоматически. Он лишь говорит event loop: «Эту корутину можно приостановить здесь». Если функция внутри выполняет блокирующую операцию, event loop не получит управления, и все асинхронное выполнение остановится.

Отсюда и практическое правило из документации asyncio: асинхронным должен быть не только внешний синтаксис, но и вся цепочка вызовов, вплоть до операций I/O.

requests.get() не возвращает awaitable-объект, поэтому await перед ней невозможен → TypeError
requests.get() не возвращает awaitable-объект, поэтому await перед ней невозможен → TypeError

Типичные ошибки новичков

Await у синхронных функций

Распространенное заблуждение — считать, что await может сделать любую функцию неблокирующей.

Синхронная функция:

  • выполняется целиком;

  • не возвращает управление event loop;

  • блокирует поток до завершения.

Если такую функцию вызвать внутри async def, она все равно выполнится синхронно и остановит event loop.

Асинхронность ради асинхронности

Еще одна типичная ошибка — переписывать код в async без понимания типа задачи. Асинхронность оправдана только там, где есть I/O-ожидание.

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

В документации FastAPI это подчеркивается отдельно: синхронный код в async-обработчиках допустим и часто предпочтительнее, если операция не блокирует I/O.

Блокирующий код внутри async

Самая опасная ошибка — использование блокирующих операций внутри асинхронных функций:

  • time.sleep() вместо asyncio.sleep();

  • синхронные HTTP-клиенты;

  • блокирующие драйверы баз данных;

  • тяжелые вычисления.

В таком случае event loop останавливается полностью. Все остальные задачи перестают выполняться, и асинхронная модель теряет смысл.

Мы собрали подборку курсов для людей с разным уровнем подготовки

Хотите стать программистом?

Мы собрали подборку курсов для людей с разным уровнем подготовкиПерейти

Частые вопросы

Где асинхронность в Python действительно нужна?

Там, где код большую часть времени ждет внешние ресурсы: в веб-серверах вроде FastAPI, при работе с сетью и API, а также при выполнении нескольких параллельных запросов.

Где асинхронность не нужна и может навредить?

В CPU-heavy задачах, простых скриптах и утилитах, а также в проектах, где async добавляет сложность без выигрыша в производительности.

Чем асинхронность отличается от многопоточности?

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

Можно ли использовать async в любом проекте?

Технически да, но практического смысла в этом нет, если приложение не упирается в I/O-ожидание.

С Python часто начинают знакомство с программированием благодаря его понятному синтаксису и предсказуемому поведению кода. Однако в языке есть темы, которые остаются сложными не только для новичков, но и для разработчиков с практическим опытом. Асинхронность — одна из таких тем. Непонимание того, зачем асинхронность нужна и как она работает, приводит к ошибкам, избыточному усложнению кода и неправильному использованию async-подхода. Все вопросы, связанные с этой темой, детально и последовательно рассматриваются на курсах программирования под руководством Михаила Шмарова.

Об эксперте

Михаил Шмаров — преподаватель Python и DevOps в Академии ТОП. Автор учебных программ и методик обучения современным языкам программирования и инструментам разработки. Автор учебника The Ultimate Docker Mastery Guide for DevOps Engineers.

Хотите лучше разобраться в вопросе?

Приходите на бесплатное занятие в вашем городе и получите ответы от практикующих экспертов

Нажимая на кнопку, я соглашаюсь на обработку персональных данных

Мы свяжемся с вами в течение дня

💫

Перезвоним и поможем подобрать курс

👍

Запишем на бесплатные пробные занятия

💯

После рассчитаем финальную стоимость с учетом возможных льгот, текущих скидок и выбранного пакета