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

Какие из следующих задач нужно переводить с многопоточного сервера на асинхронный: запись в БД, обмен с другими серверами, работа с центральным процессером?

2.7 Senior🔥 131 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

Какие задачи переводить с многопоточного на асинхронный сервер

Этот вопрос касается ключевого выбора архитектуры: когда использовать потоки (threading), а когда использовать асинхронность (asyncio). Ответ зависит от типа блокирования операции.

Классификация операций

Операции делятся на три категории:

1. I/O операции (I/O bound) — ДА, переводить на асинхронный

Это операции, которые блокируют и ждут результат от внешних источников. Асинхронность идеально подходит здесь.

Обмен с другими серверами (HTTP, TCP) — ПЕРЕВОДИТЬ НА ASYNC

# ❌ Плохо: многопоточность
import threading
import requests

def fetch_data_sync(url):
    response = requests.get(url)  # Блокирует поток
    return response.json()

threads = []
for url in urls:
    t = threading.Thread(target=fetch_data_sync, args=(url,))
    t.start()
    threads.append(t)

# ✅ Хорошо: асинхронность
import asyncio
import aiohttp

async def fetch_data_async(session, url):
    async with session.get(url) as response:
        return await response.json()

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_data_async(session, url) for url in urls]
        results = await asyncio.gather(*tasks)

asyncio.run(main())

Почему asyncio лучше:

  • Меньше памяти (легкие корутины vs тяжелые потоки)
  • Нет race conditions (один event loop, нет синхронизации)
  • Выше пропускная способность
  • Проще отладка

Запись в БД — ДА, переводить на ASYNC (если асинхронный драйвер существует)

# ❌ Многопоточность с блокирующей БД
import psycopg2
import threading

def save_user_sync(user_data):
    conn = psycopg2.connect("dbname=test user=postgres")
    cursor = conn.cursor()
    cursor.execute("INSERT INTO users (name) VALUES (%s)", (user_data['name'],))
    conn.commit()  # Блокирует поток
    conn.close()

# ✅ Асинхронность с асинхронным драйвером
import asyncpg
import asyncio

async def save_user_async(pool, user_data):
    async with pool.acquire() as conn:
        await conn.execute(
            "INSERT INTO users (name) VALUES ($1)",
            user_data['name']
        )

async def main():
    pool = await asyncpg.create_pool("postgresql://user:password@localhost/test")
    await save_user_async(pool, {'name': 'Alice'})
    await pool.close()

Чек-лист для I/O операций:

  • ✅ HTTP запросы
  • ✅ Работа с БД
  • ✅ Работа с файлами (асинхронно)
  • ✅ Работа с Redis/кешем
  • ✅ WebSocket соединения

2. CPU операции — НЕ переводить, оставить многопоточность или multiprocessing

Центральный процессор, интенсивные вычисления. Асинхронность здесь не поможет!

Работа с центральным процессером — НЕ переводить

# ❌ Неправильно: asyncio не поможет с CPU-интенсивными задачами
import asyncio

async def calculate_fibonacci(n):
    if n <= 1:
        return n
    return await calculate_fibonacci(n-1) + await calculate_fibonacci(n-2)

# asyncio создаст кучу корутин, но все они будут ждать одного потока!

Правильный подход для CPU-задач:

# Вариант 1: ProcessPoolExecutor (для CPU-bound)
import asyncio
from concurrent.futures import ProcessPoolExecutor

def cpu_intensive_task(n):
    result = 0
    for i in range(n):
        result += i * i
    return result

async def run_cpu_task():
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(
        ProcessPoolExecutor(),
        cpu_intensive_task,
        1_000_000
    )
    return result

# Вариант 2: Threading для CPU-задач (если нет GIL проблем)
import threading

def worker_thread(n):
    return cpu_intensive_task(n)

threads = [threading.Thread(target=worker_thread, args=(i,)) for i in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()

Таблица: когда что использовать

Тип задачиМногопоточностьAsyncioMultiprocessing
HTTP запросы⭐⭐⭐⭐⭐⭐⭐
Запись в БД⭐⭐⭐⭐⭐⭐⭐⭐*
CPU вычисления⭐ (GIL!)⭐⭐⭐⭐⭐
Работа с файлами⭐⭐⭐⭐⭐⭐⭐⭐⭐
WebSocket⭐⭐⭐⭐⭐⭐⭐
Кеш (Redis)⭐⭐⭐⭐⭐⭐⭐⭐

*только если есть асинхронный драйвер

Почему GIL мешает многопоточности для CPU

import threading
import time

def cpu_bound():
    return sum(i*i for i in range(50_000_000))

# Многопоточность (медленно из-за GIL)
start = time.time()
threads = []
for _ in range(4):
    t = threading.Thread(target=cpu_bound)
    t.start()
    threads.append(t)
for t in threads:
    t.join()
print(f"Многопоточность: {time.time() - start:.2f}с")  # ~4 сек

# Последовательно (примерно столько же!)
start = time.time()
for _ in range(4):
    cpu_bound()
print(f"Последовательно: {time.time() - start:.2f}с")  # ~3.8 сек

# Multiprocessing (быстро, используются несколько процессов)
from multiprocessing import Pool
start = time.time()
with Pool(4) as p:
    p.map(cpu_bound, range(4))
print(f"Multiprocessing: {time.time() - start:.2f}с")  # ~1 сек

Практический пример: смешанный сервер

import asyncio
import aiohttp
from fastapi import FastAPI
from concurrent.futures import ProcessPoolExecutor

app = FastAPI()
executor = ProcessPoolExecutor(max_workers=4)

# I/O задача — асинхронная
@app.get("/fetch-data")
async def fetch_from_api():
    async with aiohttp.ClientSession() as session:
        async with session.get("https://api.example.com/data") as resp:
            return await resp.json()

# CPU задача — в отдельном процессе
@app.get("/calculate")
async def calculate():
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(
        executor,
        expensive_calculation,
        1_000_000
    )
    return {"result": result}

def expensive_calculation(n):
    return sum(i*i for i in range(n))

Ответ на вопрос

Запись в БД: ✅ ДА, переводить на асинхронный (используйте asyncpg для PostgreSQL, motor для MongoDB)

Обмен с другими серверами: ✅ ДА, переводить на асинхронный (используйте aiohttp, httpx)

Работа с центральным процессором: ❌ НЕТ, НЕ переводить на асинхронный (используйте ProcessPoolExecutor или multiprocessing)

Золотое правило

I/O bound → asyncio
CPU bound → multiprocessing
Небольшие блокирующие операции → threading

Асинхронность ускоряет I/O операции, но не ускоряет CPU вычисления. Для CPU используйте multiprocessing, чтобы обойти GIL (Global Interpreter Lock).