Зачем нужна сопрограмма?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Сопрограммы (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 и т.д.).