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

Зачем нужна сопрограмма?

2.2 Middle🔥 161 комментариев
#Асинхронность и многопоточность

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

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

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

Сопрограммы (Coroutines) в Python

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

Проблема, которую решают корутины

Синхронный код — блокирующий

import requests
import time

def fetch_user(user_id):
    # Это блокирует поток на 2 секунды!
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()

start = time.time()
user1 = fetch_user(1)   # ждём 2 сек
user2 = fetch_user(2)   # ждём ещё 2 сек
user3 = fetch_user(3)   # ждём ещё 2 сек
print(f"Total: {time.time() - start}s")  # 6 секунд!

Асинхронный код с корутинами — неблокирующий

import asyncio
import aiohttp
import time

async def fetch_user(session, user_id):
    async with session.get(f"https://api.example.com/users/{user_id}") as response:
        return await response.json()

async def main():
    async with aiohttp.ClientSession() as session:
        # Все запросы параллельно!
        users = await asyncio.gather(
            fetch_user(session, 1),
            fetch_user(session, 2),
            fetch_user(session, 3),
        )
    return users

start = time.time()
users = asyncio.run(main())  # Все параллельно: только 2 сек!
print(f"Total: {time.time() - start}s")  # 2 секунды (не 6!)

Разница: 6 сек → 2 сек. 3x ускорение!

Как работают корутины

async def greet(name):
    print(f"Hello {name}")
    await asyncio.sleep(1)  # Приостанавливает корутину на 1 сек
    print(f"Goodbye {name}")

# Это не обычная функция!
result = greet("Alice")  # Ничего не произойдет
print(type(result))  # <class 'coroutine'>

# Нужно запустить через event loop
asyncio.run(greet("Alice"))
# Hello Alice
# (ждём 1 сек)
# Goodbye Alice

async / await синтаксис

async — указывает, что функция корутина:

async def my_function():  # Это корутина
    pass

def my_function():  # Это обычная функция
    pass

await — ждёт результат корутины, но не блокирует:

async def main():
    # await говорит: ожидай результат, но позволь другим корутинам работать
    result = await some_coroutine()
    return result

Практические применения

1. HTTP запросы (самое частое)

import aiohttp
import asyncio

async def fetch_multiple_urls(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        return responses

# 100 запросов параллельно, а не последовательно
responses = asyncio.run(fetch_multiple_urls([url1, url2, ...]))

2. WebSocket соединения (real-time)

import websockets
import asyncio

async def handle_websocket(uri):
    async with websockets.connect(uri) as websocket:
        # Слушаем сообщения без блокировки
        async for message in websocket:
            print(f"Received: {message}")

# Один поток может обрабатывать 1000+ соединений!
asyncio.run(handle_websocket("ws://example.com"))

3. Database запросы

import asyncpg
import asyncio

async def fetch_users():
    # Асинхронный драйвер для PostgreSQL
    conn = await asyncpg.connect('postgresql://user:pass@localhost/db')
    
    # 5 запросов параллельно, не последовательно
    users = await asyncio.gather(
        conn.fetch('SELECT * FROM users WHERE id = 1'),
        conn.fetch('SELECT * FROM users WHERE id = 2'),
        conn.fetch('SELECT * FROM users WHERE id = 3'),
        conn.fetch('SELECT * FROM users WHERE id = 4'),
        conn.fetch('SELECT * FROM users WHERE id = 5'),
    )
    
    await conn.close()
    return users

4. FastAPI (асинхронный вебсервер)

from fastapi import FastAPI
from fastapi.responses import JSONResponse
import aiohttp
import asyncio

app = FastAPI()

@app.get("/data")
async def get_data():
    # Асинхронный endpoint
    async with aiohttp.ClientSession() as session:
        # Параллельные запросы к внешним API
        async with session.get("https://api1.com") as r1:
            data1 = await r1.json()
        async with session.get("https://api2.com") as r2:
            data2 = await r2.json()
    
    return {"api1": data1, "api2": data2}

# Один FastAPI сервер может обрабатывать 1000+ запросов в секунду!

asyncio.gather vs asyncio.create_task

gather — ждёт все результаты

async def main():
    # Запускаю 3 корутины и жду все результаты
    results = await asyncio.gather(
        fetch_user(1),
        fetch_user(2),
        fetch_user(3),
    )
    return results  # [user1, user2, user3]

create_task — запускаю в фоне

async def main():
    # Запускаю в фоне, не жду результат сейчас
    task1 = asyncio.create_task(fetch_user(1))
    task2 = asyncio.create_task(fetch_user(2))
    
    # Делаю что-то другое
    print("Tasks started")
    
    # Потом жду результаты
    user1 = await task1
    user2 = await task2

Реальный пример: Микросервис

from fastapi import FastAPI
from contextlib import asynccontextmanager
import aiohttp
import asyncpg

app = FastAPI()

# Глобальные ресурсы
db = None
http_session = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    # На старте приложения
    global db, http_session
    db = await asyncpg.create_pool('postgresql://...')
    http_session = aiohttp.ClientSession()
    
    yield
    
    # На выключении приложения
    await db.close()
    await http_session.close()

app = FastAPI(lifespan=lifespan)

@app.get("/user/{user_id}/enriched")
async def get_enriched_user(user_id: int):
    # Все запросы параллельно!
    user_data, profile_data, weather_data = await asyncio.gather(
        db.fetchrow('SELECT * FROM users WHERE id = $1', user_id),
        http_session.get(f'https://api.example.com/profile/{user_id}').then(lambda r: r.json()),
        http_session.get('https://weather.api.com/current').then(lambda r: r.json()),
    )
    
    return {
        "user": user_data,
        "profile": profile_data,
        "weather": weather_data,
    }

# Один сервер обрабатывает 10000+ запросов в секунду

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

✅ Используй:

  • HTTP запросы (I/O bound)
  • WebSocket соединения
  • БД запросы
  • Файловые операции
  • Работа с очередями (queue, Redis)

❌ НЕ используй:

  • CPU-bound задачи (математика, обработка данных)
  • Простые синхронные операции
  • Когда нет параллелизма

CPU-bound vs I/O-bound

# ❌ I/O-bound — отлично для корутин
async def fetch_100_websites():
    # 100 HTTP запросов
    # Время ограничено сетевой задержкой, не CPU
    # Корутины дают 100x ускорение
    pass

# ❌ CPU-bound — корутины НЕ помогут
async def calculate_pi():
    # Математические вычисления
    # Время ограничено CPU, не сетью
    # Корутины не дадут ускорение (нужна многопроцессность)
    pass

Ошибки с корутинами

1. Забыл await

async def main():
    result = fetch_user(1)  # ❌ ОШИБКА! Не ждём
    print(result)  # <coroutine object ...>

    result = await fetch_user(1)  # ✅ Правильно
    print(result)  # {'id': 1, 'name': 'Alice'}

2. await в обычной функции

def get_user(user_id):  # Обычная функция
    result = await fetch_user(user_id)  # ❌ SyntaxError!
    return result

async def get_user(user_id):  # Корутина
    result = await fetch_user(user_id)  # ✅ OK
    return result

Резюме

Корутины нужны для:

  • Параллелизма — множество I/O операций одновременно
  • Производительности — 10x-100x ускорение для I/O
  • Масштабируемости — один сервер обрабатывает 10000+ соединений
  • Асинхронности — неблокирующие операции

Это ключевая технология современного Python (FastAPI, aiohttp, asyncpg и т.д.).