Как FastAPI обрабатывает синхронные и асинхронные операции?

«Как FastAPI обрабатывает синхронные и асинхронные операции?» — вопрос из категории Библиотеки и модули, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

FastAPI — это современный, высокопроизводительный веб-фреймворк для создания API на Python, который построен на основе Starlette и Pydantic. Он изначально спроектирован для работы с асинхронным кодом, но также эффективно обрабатывает и синхронные функции.

1. Асинхронные операции (async def):

  • Принцип работы: FastAPI использует стандартный синтаксис async/await Python для определения асинхронных эндпоинтов. Эти функции выполняются в основном event loop (цикле событий) и предназначены для I/O-bound задач, таких как:
    • Запросы к базам данных (с асинхронными драйверами, например, asyncpg, aiomysql).
    • Внешние HTTP-запросы (с httpx, aiohttp).
    • Чтение/запись файлов.
    • Любые операции, которые могут 'ждать' ответа от внешнего ресурса.
  • Преимущества: Не блокируют основной поток выполнения, позволяя серверу обрабатывать другие запросы, пока текущий запрос ожидает I/O. Это значительно повышает пропускную способность приложения.

Пример асинхронного эндпоинта:

from fastapi import FastAPI
import httpx # Асинхронный HTTP-клиент

app = FastAPI()

@app.get("/async-data")
async def fetch_external_data():
    """
    Асинхронный эндпоинт для получения данных из внешнего API.
    Использует httpx для неблокирующего HTTP-запроса.
    """
    async with httpx.AsyncClient() as client:
        response = await client.get("https://jsonplaceholder.typicode.com/todos/1")
        response.raise_for_status() # Выбросить исключение для плохих статусов
    return response.json()

2. Синхронные операции (def):

  • Принцип работы: Если вы определяете эндпоинт как обычную синхронную функцию (def), FastAPI автоматически запускает ее в отдельном потоке из внутреннего пула потоков (по умолчанию ThreadPoolExecutor).
  • Преимущества: Позволяет использовать блокирующие библиотеки или выполнять короткие, быстрые синхронные операции без необходимости переписывать их в асинхронном стиле.
  • Ограничения: Длительные синхронные операции (особенно CPU-bound) могут занять поток надолго, уменьшая количество доступных потоков в пуле и потенциально замедляя обработку других синхронных запросов.

Пример синхронного эндпоинта:

import time
from fastapi import FastAPI

app = FastAPI()

@app.get("/sync-message")
def get_sync_message():
    """
    Синхронный эндпоинт.
    FastAPI запустит его в отдельном потоке.
    """
    return {"message": "Это синхронный ответ от FastAPI."}

@app.get("/sync-blocking-task")
def run_blocking_task():
    """
    Пример блокирующей CPU-bound задачи.
    Не рекомендуется для длительных операций в основном потоке.
    """
    time.sleep(2) # Имитация долгой CPU-bound операции
    return {"message": "Блокирующая задача выполнена."}

Ключевые рекомендации:

  • I/O-bound задачи: Всегда используйте async def с асинхронными библиотеками (например, asyncpg, httpx, aiofiles). Это максимизирует производительность и отзывчивость вашего API.
  • CPU-bound задачи: Для длительных вычислений, которые интенсивно используют процессор, избегайте их выполнения напрямую в эндпоинтах. Вместо этого:
    • Используйте asyncio.to_thread() (доступно с Python 3.9) для явного переноса синхронной CPU-bound функции в отдельный поток.
    • Применяйте BackgroundTasks для выполнения некритичных задач после отправки ответа клиенту.
    • Рассмотрите использование внешних систем очередей задач (например, Celery, Redis Queue) для выполнения тяжелых фоновых процессов.

Понимание различий и правильное применение async def и def функций критически важно для построения высокопроизводительных и масштабируемых приложений на FastAPI.