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

Зачем использовать асинхронность для I/O bound операций?

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

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

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

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

Асинхронность для I/O bound операций — кратный рост производительности

Это основной инструмент в арсенале Python разработчика для создания высоконагруженных сервисов. Разница в производительности может быть в 10-100 раз!

Суть проблемы: блокирующие операции

Любая I/O операция (сетевой запрос, чтение файла, запрос к БД) занимает масштабируемое время — от миллисекунд до целых секунд. Во время ожидания процесс ничего не делает.

# ❌ Синхронный код (блокирующий)
import requests
import time

def fetch_user_data(user_ids: list) -> list:
    start = time.time()
    results = []
    
    for user_id in user_ids:  # 1000 пользователей
        # Каждый запрос ждёт 100ms
        response = requests.get(f'http://api/users/{user_id}')
        results.append(response.json())
    
    # Общее время: 1000 * 100ms = 100 секунд! ⏱️
    print(f"Затрачено: {time.time() - start}s")
    return results

Решение: асинхронность

# ✅ Асинхронный код
import httpx
import asyncio
import time

async def fetch_user_data(user_ids: list) -> list:
    start = time.time()
    
    # Запускаем ВСЕ запросы параллельно
    async with httpx.AsyncClient() as client:
        tasks = [
            client.get(f'http://api/users/{user_id}')
            for user_id in user_ids
        ]
        responses = await asyncio.gather(*tasks)
    
    # Общее время: 100ms (параллельно!) ⚡
    print(f"Затрачено: {time.time() - start}s")
    return [r.json() for r in responses]

Разница: 100 секунд → 100 миллисекунд = 1000 раз быстрее!

Как это работает?

Синхронный подход (блокирующий):

Запрос 1: [▓▓▓▓▓▓▓▓▓] 100ms ожидание
Запрос 2: [▓▓▓▓▓▓▓▓▓] 100ms ожидание
Запрос 3: [▓▓▓▓▓▓▓▓▓] 100ms ожидание
─────────────────────────────────
Всего: 300ms (процесс заморожен)

Асинхронный подход:

Запрос 1: ▓▓▓▓▓▓▓▓▓
Запрос 2:   ▓▓▓▓▓▓▓▓▓
Запрос 3:     ▓▓▓▓▓▓▓▓▓
─────────────────────────────────
Всего: 100ms (параллельно!)

Пока один запрос ждёт ответа, процесс обрабатывает другие запросы.

Практический пример: FastAPI

from fastapi import FastAPI
import httpx
import asyncio

app = FastAPI()

# ❌ Синхронный обработчик (блокирует сервер)
@app.get("/sync/user/{user_id}")
def get_user_sync(user_id: int):
    # Блокирует весь сервер на 1 секунду
    import requests
    response = requests.get(f'https://api.example.com/users/{user_id}')
    return response.json()

# ✅ Асинхронный обработчик (масштабируется)
@app.get("/async/user/{user_id}")
async def get_user_async(user_id: int):
    # Не блокирует сервер — отпускает нить
    async with httpx.AsyncClient() as client:
        response = await client.get(f'https://api.example.com/users/{user_id}')
    return response.json()

С синхронным кодом: сервер может обработать ~1 запрос в секунду
С асинхронным кодом: сервер обработает 1000+ запросов в секунду

Практические сценарии

1. Веб-краулинг

async def crawl_urls(urls: list) -> dict:
    async with httpx.AsyncClient() as client:
        # Параллельно загружаем 100 страниц
        tasks = [client.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
    return {url: r.text for url, r in zip(urls, responses)}

# Синхронно: 100 * 1s = 100 секунд
# Асинхронно: ~1-2 секунды

2. Запросы к БД

# ❌ Синхронно (блокирующее подключение)
from sqlalchemy import create_engine

engine = create_engine('postgresql://localhost/db')
with engine.connect() as conn:
    for user_id in range(1000):
        result = conn.execute(f"SELECT * FROM users WHERE id={user_id}")
        # Ждём ответа БД
# ✅ Асинхронно (неблокирующее подключение)
from sqlalchemy.ext.asyncio import create_async_engine

engine = create_async_engine('postgresql+asyncpg://localhost/db')
async with engine.begin() as conn:
    tasks = [
        conn.execute(f"SELECT * FROM users WHERE id={user_id}")
        for user_id in range(1000)
    ]
    results = await asyncio.gather(*tasks)

3. Отправка писем и уведомлений

async def send_notifications(user_emails: list):
    tasks = [
        send_email_async(email)
        for email in user_emails
    ]
    # Отправляем все письма параллельно
    await asyncio.gather(*tasks)

# Синхронно: 1000 * 0.5s = 500 секунд
# Асинхронно: ~5 секунд (при параллелизме 100)

Когда НЕ нужна асинхронность?

CPU-bound операции:

# Асинхронность тут НЕ поможет
def compute_factorial(n):
    # Чистые вычисления
    return math.factorial(n)  # ~1ms CPU время

# Для CPU-bound используем multiprocessing
from multiprocessing import Pool
with Pool(4) as p:
    results = p.map(compute_factorial, numbers)

Сравнение подходов

ОперацияСинхронноАсинхронноMultiprocessing
I/O операции (сеть, БД)МедленноБыстро ✅Нет смысла
Вычисления (CPU)Быстро ✅Не помогаетБыстро ✅
Комбо (I/O + вычисления)МедленноБыстро ✅Ещё быстрее

Механика асинхронности в Python

import asyncio

async def task(name: str, duration: int):
    print(f"{name} начало")
    await asyncio.sleep(duration)  # Имитация I/O
    print(f"{name} завершено")

async def main():
    # Все задачи выполняются параллельно
    await asyncio.gather(
        task("A", 1),
        task("B", 2),
        task("C", 1)
    )
    # Общее время: 2 секунды (не 4!)

asyncio.run(main())

Выпуск:

A начало
B начало
C начало
A завершено
C завершено
B завершено

Итог

Зачем использовать асинхронность для I/O:

Производительность — увеличиваем throughput в 10-100 раз
Масштабируемость — один сервер обрабатывает 1000+ одновременных запросов
Экономия ресурсов — не создаём отдельный процесс на каждый запрос
Responsive система — не блокируем пользователей

В Python это практически стандарт для:

  • FastAPI приложений
  • Веб-серверов
  • Систем обработки данных
  • Микросервисов

Это не опция — это необходимость в modern backend разработке.