← Назад к вопросам

Как устроен механизм работы корутин?

3.0 Senior🔥 191 комментариев
#Python Core#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм работы корутин в Python

Корутины — это функции, которые могут приостанавливать своё выполнение и передавать управление другим корутинам, позволяя однопоточному коду работать с множеством одновременных операций. Это один из главных инструментов асинхронного программирования в современном Python.

Эволюция корутин

В Python существует несколько поколений корутин:

  1. Generator-based корутины (Python 3.4) — использовали @coroutine и yield from
  2. Native корутины (Python 3.5+) — async/await синтаксис
  3. Асинхронные генераторы (Python 3.6+) — комбинация async и yield

Как работают native корутины (async/await)

import asyncio

async def fetch_data(url):
    print(f"Начало загрузки: {url}")
    await asyncio.sleep(2)
    print(f"Завершена загрузка: {url}")
    return f"Данные с {url}"

async def main():
    task1 = fetch_data("http://api.example.com/1")
    task2 = fetch_data("http://api.example.com/2")
    results = await asyncio.gather(task1, task2)
    print(results)

asyncio.run(main())

Ключевые концепции

Event Loop (цикл событий)

Это сердце асинхронной программы:

  • Отслеживает все корутины и задачи
  • Когда корутина вызывает await, она отдаёт управление
  • Loop проверяет другие готовые корутины
  • Когда I/O завершится, корутина пробуждается
import asyncio

async def example():
    loop = asyncio.get_running_loop()
    print(f"Текущий цикл: {loop}")

asyncio.run(example())

await — точка перехвата

Когда вы пишете await, вы говорите: "Приостанови меня и отдай управление другим". Это может быть:

  • Другая корутина
  • I/O операция (сеть, диск)
  • Таймер
async def example():
    await asyncio.sleep(1)  # OK
    result = await some_async_func()  # OK
    # result = await 42  # ОШИБКА

Task vs Coroutine

Корутина — отложенное вычисление, не запущено:

async def my_coroutine():
    await asyncio.sleep(1)
    return "готово"

coro = my_coroutine()  # Создана, но НЕ запущена
print(type(coro))  # coroutine

Task — обёртка над корутиной в event loop:

async def main():
    task = asyncio.create_task(my_coroutine())
    result = await task
    results = await asyncio.gather(my_coroutine(), my_coroutine())

asyncio.run(main())

Управление множеством операций

async def main():
    # gather — все параллельно
    results = await asyncio.gather(
        fetch_data("url1"),
        fetch_data("url2"),
        fetch_data("url3")
    )
    
    # wait — контроль завершения
    tasks = [asyncio.create_task(fetch_data(f"url{i}")) for i in range(3)]
    done, pending = await asyncio.wait(
        tasks,
        return_when=asyncio.FIRST_COMPLETED
    )
    
    # as_completed — обработка по мере завершения
    for coro in asyncio.as_completed(tasks):
        result = await coro
        print(f"Готово: {result}")

Важно: асинхронный ≠ многопоточный

Асинхронный код выполняется в одном потоке! Это распределение времени между операциями:

import asyncio
import threading

async def worker(id):
    print(f"Worker {id} на потоке {threading.current_thread().name}")
    await asyncio.sleep(1)

async def main():
    await asyncio.gather(worker(1), worker(2), worker(3))

asyncio.run(main())

Это эффективнее многопоточности для I/O:

  • Нет overhead переключения потоков
  • Нет race conditions
  • Нет deadlock'ов

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

# Забыл await
async def main():
    result = fetch_data("url")  # Вернётся coroutine объект!

# Блокирующий код в async
async def main():
    time.sleep(10)  # Заморозит весь event loop!
    # Правильно: await asyncio.sleep(10)

# Смешивание sync и async
def sync_function():
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(async_func())  # Очень плохо

Пример с httpx для параллельных запросов

import asyncio
import httpx

async def fetch_url(client, url):
    response = await client.get(url)
    return response.status_code

async def main():
    urls = ["https://example.com", "https://google.com", "https://github.com"]
    
    async with httpx.AsyncClient() as client:
        status_codes = await asyncio.gather(
            *[fetch_url(client, url) for url in urls]
        )
        print(f"Статусы: {status_codes}")

asyncio.run(main())

Вместо 3 последовательных запросов за 3 секунды — все 3 параллельно за ~1 секунду.

Заключение

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