Логотип YeaHub

База вопросов

Собеседования

Тренажёр

База ресурсов

Обучение

Навыки

Войти

Выбери, каким будет IT завтра — вместе c нами!

YeaHub — это полностью открытый проект, призванный объединить и улучшить IT-сферу. Наш исходный код доступен для просмотра на GitHub. Дизайн проекта также открыт для ознакомления в Figma.

© 2026 YeaHub

Документы

Медиа

Назад
Вопрос про Python: asyncio, coroutine, CPU-bound, event loop, GIL, blocking

Что произойдет, если coroutine выполняет CPU-bound задачу?

Вопрос проверяет понимание ограничений асинхронного программирования в Python и того, как CPU-bound задачи блокируют цикл событий, снижая производительность.

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

Если корутина выполняет CPU-bound задачу (например, сложные вычисления, обработку изображений), она блокирует цикл событий (event loop). Это происходит потому, что asyncio работает в одном потоке, и пока корутина выполняет вычисления, она не отдает управление обратно в event loop. В результате все остальные корутины "замирают" и ждут, пока CPU-bound задача не завершится. Это сводит на нет преимущества асинхронности, так как задачи выполняются последовательно, а не конкурентно.

Длинный ответ

Асинхронное программирование в Python с помощью модуля asyncio отлично подходит для I/O-bound операций, таких как запросы к сети или чтение файлов, где корутины могут "отдавать" управление, ожидая завершения операции. Однако для задач, интенсивно использующих процессор (CPU-bound), эта модель ломается.

Почему CPU-bound задача блокирует event loop?

Цикл событий asyncio работает в одном потоке. Корутина выполняется до тех пор, пока не встретит операцию await, которая сигнализирует о возможной блокировке (например, ожидании ответа от сети). В этот момент управление возвращается в event loop, который может запустить другую корутину. CPU-bound задача, по своей природе, не содержит таких точек await — она просто выполняет вычисления, не отдавая управление. Таким образом, event loop "застывает" до завершения этих вычислений.

Пример кода, демонстрирующий проблему

import asyncio
import time

# CPU-bound функция (имитация тяжелых вычислений)
def cpu_intensive_task():
    result = 0
    for i in range(10**7):
        result += i
    return result

async def cpu_bound_coroutine():
    print("Начало CPU-bound задачи...")
    # Прямой вызов блокирующей функции — ОШИБКА!
    result = cpu_intensive_task()
    print(f"CPU-bound задача завершена: {result}")

async def another_coroutine():
    for i in range(5):
        print(f"Другая корутина работает: {i}")
        await asyncio.sleep(0.1)  # Точка await, где управление возвращается

async def main():
    # Запускаем обе корутины "одновременно"
    await asyncio.gather(
        cpu_bound_coroutine(),
        another_coroutine()
    )

# Запуск
asyncio.run(main())

В этом примере another_coroutine не будет выполняться, пока не завершится cpu_intensive_task, потому что последняя не содержит await и блокирует поток.

Как решить проблему?

Для выполнения CPU-bound задач в асинхронном приложении нужно вынести их в отдельные потоки или процессы, чтобы не блокировать основной поток event loop. Модуль asyncio предоставляет для этого:

  • loop.run_in_executor() — запуск блокирующего кода в пуле потоков или процессов.
  • Использование модуля concurrent.futures для создания собственного исполнителя (executor).

Исправленный вариант с использованием исполнителя:

import asyncio
from concurrent.futures import ProcessPoolExecutor

def cpu_intensive_task():
    result = 0
    for i in range(10**7):
        result += i
    return result

async def cpu_bound_coroutine():
    print("Начало CPU-bound задачи...")
    loop = asyncio.get_running_loop()
    # Запускаем в отдельном процессе
    result = await loop.run_in_executor(None, cpu_intensive_task)
    print(f"CPU-bound задача завершена: {result}")

async def another_coroutine():
    for i in range(5):
        print(f"Другая корутина работает: {i}")
        await asyncio.sleep(0.1)

async def main():
    await asyncio.gather(
        cpu_bound_coroutine(),
        another_coroutine()
    )

asyncio.run(main())

Теперь cpu_intensive_task выполняется в отдельном потоке/процессе, и event loop может переключаться на другие корутины.

Вывод: CPU-bound задачи в корутинах блокируют цикл событий, уничтожая преимущества асинхронности. Для их выполнения используйте run_in_executor или выносите в отдельные процессы, особенно в высоконагруженных I/O-приложениях, таких как веб-серверы на aiohttp или FastAPI.

Уровень

  • Рейтинг:

    4

  • Сложность:

    6

Навыки

  • Python

    Python

  • Node.js

    Node.js

Ключевые слова

#asyncio

#coroutine

#CPU-bound

#event loop

#GIL

#blocking

Подпишись на Python Developer в телеграм