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

Что такое корутина?

2.0 Middle🔥 221 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

Корутины в Python

Определение

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

История: от генераторов к async/await

# Этап 1: Генераторы (yield) — первая форма корутин
def simple_generator():
    print("Начало")
    yield 1
    print("Середина")
    yield 2
    print("Конец")
    yield 3

gen = simple_generator()
print(next(gen))   # Начало, затем 1
print(next(gen))   # Середина, затем 2
print(next(gen))   # Конец, затем 3

# Этап 2: Асинхронные корутины (async/await) — современный способ
async def async_coroutine():
    print("Начало")
    await asyncio.sleep(1)
    print("После ожидания")
    return "Результат"

Простые корутины с yield

# Корутина как производитель данных
def count_up_to(max):
    count = 1
    while count <= max:
        print(f"Генерирую {count}")
        yield count
        count += 1

for num in count_up_to(3):
    print(f"Получил: {num}")

# Вывод:
# Генерирую 1
# Получил: 1
# Генерирую 2
# Получил: 2
# Генерирую 3
# Получил: 3

Асинхронные корутины с 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():
    # Способ 1: ждём результат
    result = await fetch_data("https://api.example.com")
    print(result)
    
    # Способ 2: запускаем параллельно
    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())

Ключевые различия

# ФУНКЦИЯ — выполняется полностью от начала до конца
def regular_function():
    return "Результат"

result = regular_function()  # Сразу получаем результат

# ГЕНЕРАТОР — может быть приостановлен
def generator_function():
    yield "Первое"
    yield "Второе"
    yield "Третье"

gen = generator_function()  # Не выполнилось! Просто создали объект
print(next(gen))  # Первое — выполнилось до первого yield
print(next(gen))  # Второе — выполнилось до второго yield

# АСИНХРОННАЯ КОРУТИНА — выполняется с await
async def async_function():
    print("Старт")
    await asyncio.sleep(1)
    print("После ожидания")
    return "Результат"

result = await async_function()  # Требует await, может выполняться параллельно

Практический пример: параллельная загрузка

import asyncio
import time

async def fetch_user(user_id):
    print(f"Загружаю пользователя {user_id}")
    await asyncio.sleep(2)  # Имитация API запроса
    print(f"Загрузил пользователя {user_id}")
    return {"id": user_id, "name": f"User{user_id}"}

async def fetch_posts(user_id):
    print(f"Загружаю посты пользователя {user_id}")
    await asyncio.sleep(3)  # Имитация API запроса
    print(f"Загрузил посты пользователя {user_id}")
    return [{"id": i, "user_id": user_id} for i in range(3)]

# ПЛОХО: последовательно (5 секунд)
async def bad_approach():
    start = time.time()
    user = await fetch_user(1)
    posts = await fetch_posts(1)
    print(f"Время: {time.time() - start:.2f}с")  # ~5 секунд

# ХОРОШО: параллельно (3 секунды)
async def good_approach():
    start = time.time()
    user, posts = await asyncio.gather(
        fetch_user(1),
        fetch_posts(1)
    )
    print(f"Время: {time.time() - start:.2f}с")  # ~3 секунды

asyncio.run(good_approach())

Корутины vs Потоки

import threading
import asyncio
import time

# ПОТОКИ: истинный параллелизм, но тяжело управлять
def thread_task():
    for i in range(3):
        print(f"Поток: {i}")
        time.sleep(1)

threads = [threading.Thread(target=thread_task) for _ in range(3)]
for t in threads:
    t.start()
for t in threads:
    t.join()
# Каждый поток занимает память, контекстное переключение дорогое

# КОРУТИНЫ: кооперативная многозадачность, легко управлять
async def coroutine_task():
    for i in range(3):
        print(f"Корутина: {i}")
        await asyncio.sleep(1)

async def main():
    await asyncio.gather(
        coroutine_task(),
        coroutine_task(),
        coroutine_task()
    )

asyncio.run(main())
# Все корутины работают в одном потоке, но без блокировок

Цепочка вызовов корутин

import asyncio

async def step1():
    print("Шаг 1: подготовка")
    await asyncio.sleep(1)
    return "Данные из шага 1"

async def step2(data):
    print(f"Шаг 2: обработка '{data}'")
    await asyncio.sleep(1)
    return f"Обработано: {data}"

async def step3(data):
    print(f"Шаг 3: сохранение '{data}'")
    await asyncio.sleep(1)
    return "Сохранено"

async def pipeline():
    # Последовательная цепочка
    data1 = await step1()
    data2 = await step2(data1)
    result = await step3(data2)
    print(f"Итог: {result}")

asyncio.run(pipeline())

Обработка ошибок в корутинах

import asyncio

async def risky_operation():
    await asyncio.sleep(1)
    raise ValueError("Ошибка в операции!")

async def safe_operation():
    try:
        result = await risky_operation()
    except ValueError as e:
        print(f"Поймали ошибку: {e}")
        return None
    return result

asyncio.run(safe_operation())

Таймауты

import asyncio

async def slow_operation():
    await asyncio.sleep(10)
    return "Результат"

async def main():
    try:
        result = await asyncio.wait_for(slow_operation(), timeout=2.0)
    except asyncio.TimeoutError:
        print("Операция заняла слишком много времени!")

asyncio.run(main())

Сравнение синтаксиса

# Генератор (кооперативная корутина)
def generator():
    yield 1
    yield 2

# Асинхронная функция
async def async_func():
    await asyncio.sleep(1)
    return "Результат"

# Главное отличие:
# - yield: используется с for или next()
# - await: используется в async функциях
# - async function: выполняется в event loop

Когда использовать корутины?

# ✅ ИСПОЛЬЗУЙ корутины когда:
# - I/O операции (сетевые запросы, файлы)
# - Множество одновременных операций
# - Нужна асинхронность без потоков

# ❌ НЕ используй корутины когда:
# - CPU-bound задачи (использовать multiprocessing)
# - Простая синхронная логика (усложнишь)
# - Нужен истинный параллелизм (использовать потоки)

Итоговое резюме

  • Корутина — функция, которая может быть приостановлена и возобновлена
  • yield — для генераторов (простые корутины)
  • async/await — для асинхронных корутин (современный стандарт)
  • Event loop — система, управляющая выполнением корутин
  • Преимущество — множество I/O операций без потоков
  • asyncio — стандартная библиотека для работы с корутинами