> Что такое coroutine и как он работает в Python (Python)

Уровень: junior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: Домклик, Sunlight

Стек: Python

> Пример ответа

Короткий ответ

Coroutine - это функция, выполнение которой можно приостановить и возобновить позже, сохраняя её состояние. В Python coroutine создаётся через async def и управляется event loop. В отличие от обычной функции, coroutine не выполняется сразу при вызове - она возвращает объект coroutine, который нужно запустить через await или event loop. Это позволяет писать конкурентный код без многопоточности, эффективно работая с I/O-bound задачами.

Подробное объяснение

Coroutine в Python - это специальный вид функции, определённой с помощью async def. При вызове такая функция не выполняет своё тело, а возвращает объект coroutine. Выполнение начинается только когда coroutine передаётся в event loop или вызывается через await.

Ключевые механизмы:

  • await - приостанавливает выполнение coroutine до завершения другой awaitable-операции (другой coroutine, Future, Task). В момент await управление возвращается в event loop.
  • Event loop - центральный диспетчер, который отслеживает состояние всех coroutine и переключает выполнение между ними, когда одна из них блокируется на I/O.
  • Task - обёртка над coroutine, которая планирует её выполнение в event loop. Создаётся через asyncio.create_task().
  • State - состояние coroutine (локальные переменные, счётчик команд) сохраняется между приостановками, поэтому при возобновлении выполнение продолжается с места последнего await.

Отличие от генераторов: генераторы тоже приостанавливаются через yield, но coroutine предназначены для конкурентного выполнения и управляются event loop, а не вручную через next().

На практике

В backend-разработке coroutine используются для обработки множества одновременных I/O операций: HTTP-запросов, работы с БД, чтения файлов. Например, веб-сервер на FastAPI или aiohttp обрабатывает тысячи соединений в одном потоке, переключаясь между coroutine, когда одна из них ожидает ответа от БД или внешнего API.

Важно понимать: coroutine не ускоряют CPU-bound задачи. Если внутри coroutine выполняется тяжёлая синхронная работа, она заблокирует весь event loop. Для CPU-bound задач нужны отдельные процессы или потоки.

Пример кода

PYTHON
import asyncio
async def fetch_data(url):
print(f"Начало загрузки {url}")
await asyncio.sleep(1) # имитация I/O операции
print(f"Загрузка {url} завершена")
return f"Данные из {url}"
async def main():
# Создаём две задачи, которые выполняются конкурентно
task1 = asyncio.create_task(fetch_data("url1"))
task2 = asyncio.create_task(fetch_data("url2"))
# Ожидаем завершения обеих задач
results = await asyncio.gather(task1, task2)
print(results)
asyncio.run(main())

Вывод:

Начало загрузки url1
Начало загрузки url2
Загрузка url1 завершена
Загрузка url2 завершена
['Данные из url1', 'Данные из url2']

Обратите внимание: обе загрузки стартуют одновременно, а не последовательно.

Как отвечать на собеседовании

Начните с простого определения, затем покажите понимание механики: async def, await, event loop. Обязательно упомяните, что coroutine - это не поток и не процесс, а способ конкурентного выполнения в одном потоке. Приведите пример из практики: обработка HTTP-запросов в FastAPI. Если спросят про отличие от генераторов - скажите, что генераторы управляются вручную через next(), а coroutine - через event loop. Хорошо показать понимание ограничений: coroutine не помогают для CPU-bound задач.

Что проверяет интервьюер

Интервьюер проверяет:

  • Понимаете ли вы разницу между синхронным и асинхронным кодом.
  • Знаете ли вы, как создаётся coroutine (async def) и как запускается (await, asyncio.run, create_task).
  • Понимаете ли вы роль event loop и почему await передаёт управление.
  • Понимаете ли вы, что coroutine - это не магия, а механизм кооперативной многозадачности: coroutine сама решает, когда приостановиться.
  • Умеете ли вы объяснить, когда coroutine полезны, а когда нет.

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

  • Забывают про await: вызов fetch_data("url") без await просто создаёт объект coroutine, но не выполняет его. Это частая ошибка новичков.
  • Путают coroutine с потоками: coroutine выполняются в одном потоке, поэтому нет гонок данных, но и нет параллелизма для CPU-bound задач.
  • Блокируют event loop: вызов time.sleep() или синхронного requests внутри coroutine блокирует все остальные coroutine. Нужно использовать asyncio.sleep() и асинхронные библиотеки.
  • Не используют create_task: если просто написать await fetch_data("url1") и затем await fetch_data("url2"), они выполнятся последовательно, а не конкурентно.
  • Не понимают, что asyncio.run() создаёт и закрывает event loop: нельзя вызывать asyncio.run() внутри уже работающего event loop.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью