В чем разница между асинхронным и синхронным ендпойтом в FastAPI?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между асинхронным и синхронным эндпойтом в FastAPI
Это критически важный вопрос для понимания производительности FastAPI. FastAPI поддерживает оба подхода, и выбор между ними зависит от характера операций в эндпойте.
Синхронный эндпойт
Синхронный эндпойт — это обычная функция Python, которая выполняется последовательно, блокируя поток выполнения на время работы.
from fastapi import FastAPI
import time
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
# Блокирует поток на 2 секунды
time.sleep(2)
return {"id": user_id, "name": "John"}
Когда клиент делает запрос:
- FastAPI выделяет один из потоков из пула рабочих потоков
- Функция выполняется полностью до конца
- Поток занят и не может обрабатывать другие запросы
- Когда функция завершается, поток освобождается
Асинхронный эндпойт
Асинхронный эндпойт — это async функция, которая может "приостанавливаться" и "возобновляться", освобождая поток во время ожидания.
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/users/{user_id}")
async def get_user(user_id: int):
# Освобождает поток на 2 секунды
await asyncio.sleep(2)
return {"id": user_id, "name": "John"}
Когда клиент делает запрос:
- FastAPI запускает async функцию
- Когда встречается
await, функция приостанавливается - Поток освобождается и может обрабатывать другие запросы
- Когда операция завершается, функция возобновляется
Основные различия
| Параметр | Синхронный | Асинхронный |
|---|---|---|
| Синтаксис | def function() | async def function() |
| Ожидание | time.sleep() | await asyncio.sleep() |
| Блокирование | Блокирует поток | Освобождает поток |
| Одновременные запросы | N запросов = N потоков | Множество запросов = 1 поток |
| Потребление памяти | Больше (один поток на запрос) | Меньше (один поток на многие) |
| Производительность I/O | Низкая (если много операций БД) | Высокая (идеально для I/O) |
| CPU-bound операции | Хороший выбор | Плохой выбор |
Практический пример: обработка 1000 запросов
Синхронный вариант
from fastapi import FastAPI
import time
app = FastAPI()
@app.get("/sync-endpoint")
def sync_endpoint():
# Каждый запрос занимает 1 секунду
time.sleep(1)
return {"message": "Done"}
# 1000 запросов × 1 сек = 1000 секунд
# Если у вас 4 рабочих потока: 1000 / 4 = 250 секунд
Асинхронный вариант
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/async-endpoint")
async def async_endpoint():
# Каждый запрос ждёт 1 секунду, но освобождает поток
await asyncio.sleep(1)
return {"message": "Done"}
# 1000 запросов × 0 сек (с точки зрения потока) = ~1 секунда
# Все 1000 запросов могут выполняться одновременно!
Когда использовать синхронный?
Синхронный эндпойт используй, если операция:
- CPU-bound (вычисление, обработка данных)
- Использует синхронные библиотеки (например, обычный
requests) - Очень быстрая (< 10 мс)
import requests # Синхронная библиотека
@app.get("/calculate")
def calculate():
# CPU-bound операция
result = sum(range(1000000))
return {"result": result}
@app.get("/sync-api")
def call_external_api():
# Синхронный запрос (может быть медленным!)
response = requests.get("https://api.example.com/data")
return response.json()
Когда использовать асинхронный?
Асинхронный эндпойт используй, если операция:
- I/O-bound (запросы в БД, HTTP запросы)
- Использует асинхронные библиотеки (
aiohttp,sqlalchemy async) - Может быть медленной (> 100 мс)
import httpx
from sqlalchemy.ext.asyncio import AsyncSession
@app.get("/async-api")
async def call_external_api():
# Асинхронный HTTP запрос
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
return response.json()
@app.get("/async-db")
async def get_user_async(session: AsyncSession, user_id: int):
# Асинхронный запрос в БД
user = await session.execute(
select(User).where(User.id == user_id)
)
return user.scalar()
Реальный пример: параллельные запросы
from fastapi import FastAPI
import httpx
import asyncio
app = FastAPI()
# ПЛОХО - синхронно, очень медленно
@app.get("/sync-multiple")
def sync_multiple():
urls = [f"https://api.example.com/data/{i}" for i in range(10)]
results = []
for url in urls:
# Каждый запрос ждёт 1 сек = 10 секунд всего
response = requests.get(url)
results.append(response.json())
return {"results": results}
# ХОРОШО - асинхронно, параллельно
@app.get("/async-multiple")
async def async_multiple():
urls = [f"https://api.example.com/data/{i}" for i in range(10)]
async with httpx.AsyncClient() as client:
# Все 10 запросов выполняются параллельно = ~1 сек всего
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
results = [r.json() for r in responses]
return {"results": results}
Важный нюанс: чистые функции
Если в эндпойте используется синхронная (не асинхронная) функция с I/O операциями, FastAPI автоматически запускает её в отдельном потоке:
@app.get("/blocking-but-safe")
def blocking_operation():
# Это запустится в отдельном потоке, не блокируя главный event loop
import requests
response = requests.get("https://api.example.com/data")
return response.json()
# FastAPI использует ThreadPoolExecutor для синхронных операций
# Это медленнее, чем асинхронный вариант, но безопаснее
Чек-лист: выбор между async и sync
┌─ Это I/O операция?
│ ├─ Да → Используй async + await
│ └─ Нет → Идёшь дальше
│
└─ Это CPU-bound операция?
├─ Да → Используй sync def
└─ Нет → Используй async def (на всякий случай)
Итоговый вывод
- Синхронный эндпойт блокирует поток и хорош для быстрых CPU-операций
- Асинхронный эндпойт освобождает поток и хорош для I/O-операций
- Для I/O-bound операций (БД, HTTP) обязательно используй async — это даст огромный прирост производительности
- FastAPI может смешивать оба подхода в одном приложении
- Асинхронная версия может быть на 100x быстрее для I/O-bound операций!