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

Какие плюсы и минусы asyncio?

2.0 Middle🔥 171 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

Плюсы и минусы asyncio в Python

asyncio — это встроенная библиотека для асинхронного программирования в Python. Она позволяет писать кооперативный многозадачный код с использованием async/await синтаксиса.

Плюсы asyncio

1. Простота синтаксиса

Вместо callbacks (как в JavaScript), Python использует async/await:

# Асинхронный способ
async def fetch_user(user_id):
    response = await http_client.get(f'/users/{user_id}')
    return response.json()

# Читается как синхронный код, но работает асинхронно
user = await fetch_user(123)

Это намного читаемее чем callbacks, которые создают "callback hell".

2. Встроено в стандартную библиотеку

Не нужно устанавливать внешние зависимости. asyncio — это часть Python:

import asyncio

async def main():
    await asyncio.sleep(1)
    print("Done")

asyncio.run(main())

3. Эффективное использование памяти

По сравнению с потоками (threads), async задачи используют значительно меньше памяти:

# Потоки (threads)
# Каждый поток занимает ~1-2 MB памяти
threads = [threading.Thread(target=worker) for _ in range(10000)]
# На 10000 потоков: 10-20 GB памяти!

# asyncio
# Каждая корутина занимает ~0.01 MB
corutines = [async_worker() for _ in range(10000)]
# На 10000 корутин: ~100 MB памяти

4. Подходит для I/O-bound операций

asyncio идеален когда приложение ждет (I/O, network, database):

async def scrape_urls(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)  # Все параллельно
        return results

# 100 URL, каждый 1 секунда
# Синхронно: 100 секунд
# asyncio: 1 секунда

5. Контроль над выполнением

Вы точно знаете где происходит переключение контекста (await точка):

async def task():
    print("Step 1")      # Выполняется
    await asyncio.sleep(1)  # Здесь может переключиться
    print("Step 2")      # Потом выполняется
    # Между Step 1 и Step 2 может выполниться другая коруина

В отличие от потоков, где переключение может произойти в любой момент (race conditions).

6. Популярные фреймворки

Многие современные фреймворки построены на asyncio:

  • FastAPI
  • aiohttp
  • asyncpg
  • motor (async MongoDB driver)
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import create_async_engine

app = FastAPI()
engine = create_async_engine('postgresql+asyncpg://...')

@app.get('/users/{user_id}')
async def get_user(user_id: int):
    async with engine.begin() as conn:
        result = await conn.execute(...)
        return result

Минусы asyncio

1. Single-threaded — не использует несколько CPU ядер

Это главное ограничение asyncio:

async def cpu_bound_task():
    # Это будет ОЧЕНЬ медленно
    result = sum(i*i for i in range(10**8))
    return result

# asyncio работает в одном потоке
# Все процессорные вычисления блокируют event loop

Для CPU-bound задач нужно использовать asyncio.to_thread() или multiprocessing:

import asyncio

async def cpu_bound_task_with_thread():
    result = await asyncio.to_thread(expensive_computation)
    return result

def expensive_computation():
    return sum(i*i for i in range(10**8))

2. Сложность отладки

Debugger может запутаться при переключении задач:

async def task1():
    print("Start 1")
    await asyncio.sleep(1)  # Отступает в другую задачу
    print("End 1")

async def task2():
    print("Start 2")
    await asyncio.sleep(1)
    print("End 2")

# Обычно вывод:
# Start 1
# Start 2
# End 1
# End 2

# Но в debugger это путает (нет четкого call stack)

3. Все библиотеки должны быть async

Если одна библиотека не async, она заблокирует весь event loop:

import requests  # Синхронная библиотека
import aiohttp   # Асинхронная библиотека

async def bad_code():
    # ПЛОХО! Блокирует весь event loop на 5 секунд
    response = requests.get('http://example.com', timeout=5)
    return response.json()

async def good_code():
    # ХОРОШО! Не блокирует
    async with aiohttp.ClientSession() as session:
        async with session.get('http://example.com') as resp:
            return await resp.json()

4. Ошибки могут быть сложными

Race conditions все еще возможны:

data = 0

async def increment():
    global data
    temp = data      # Читаем
    await asyncio.sleep(0)  # Здесь переключается!
    data = temp + 1  # Пишем

async def main():
    global data
    await asyncio.gather(
        increment(),
        increment(),
        increment()
    )
    print(data)  # Может быть 1, 2 или 3 вместо 3!
    # Нужно использовать Lock

Решение — используй asyncio.Lock:

data = 0
lock = asyncio.Lock()

async def increment():
    global data
    async with lock:  # Критическая секция
        temp = data
        await asyncio.sleep(0)
        data = temp + 1

5. Дополнительная сложность для новичков

async/await требует понимания:

  • Что такое event loop
  • Что такое корутины
  • Когда происходит переключение контекста
  • Как правильно использовать await
# ОШИБКА - забыл await
async def fetch():
    return await http.get('...')

async def main():
    result = fetch()  # Это корутина, не результат!
    print(result)  # <coroutine object fetch at 0x...>

# ПРАВИЛЬНО
async def main():
    result = await fetch()  # Теперь результат
    print(result)  # Реальные данные

6. Затруднения с множественными event loop'ами

В одном потоке может быть только один event loop:

# Ошибка в многопоточном приложении
loop = asyncio.new_event_loop()

def thread_function():
    asyncio.set_event_loop(loop)  # Может быть конфликт
    loop.run_until_complete(async_task())

thread = threading.Thread(target=thread_function)
thread.start()

7. Сложность с библиотеками старого кода

Нельзя просто так смешивать async и sync код:

# Модуль с синхронным кодом
class DatabaseConnection:
    def query(self, sql):  # Синхронный
        return db.execute(sql)

# Не можно просто сделать await
async def fetch():
    result = await db.query('SELECT ...')  # TypeError!

Когда использовать asyncio

Идеально для:

  • Веб-приложения с высокой конкурентностью (FastAPI, aiohttp)
  • Веб-скрейпинг множества URL
  • Чтение/запись множества файлов параллельно
  • Работа с WebSocket соединениями
  • Микросервисы с множеством внешних API вызовов

Плохо для:

  • CPU-intensive задачи (используй multiprocessing)
  • Сложное параллельное программирование (используй threading)
  • Работа с legacy синхронным кодом

Пример реального использования

from fastapi import FastAPI
import asyncpg
import aiohttp

app = FastAPI()

# Connection pool
db_pool = None

@app.on_event('startup')
async def startup():
    global db_pool
    db_pool = await asyncpg.create_pool('postgresql://...')

@app.on_event('shutdown')
async def shutdown():
    await db_pool.close()

@app.get('/users/{user_id}')
async def get_user(user_id: int):
    # Асинхронная база данных
    async with db_pool.acquire() as conn:
        user = await conn.fetchrow('SELECT * FROM users WHERE id = $1', user_id)
    
    # Асинхронный HTTP запрос
    async with aiohttp.ClientSession() as session:
        async with session.get(f'https://api.external.com/users/{user_id}') as resp:
            external_data = await resp.json()
    
    return {**user, **external_data}

Вывод

asyncio — отличный инструмент для I/O-bound операций с хорошей производительностью и читаемостью. Но это не серебряная пуля. Понимай его ограничения (single-threaded, требует async библиотек) и используй правильно.

Какие плюсы и минусы asyncio? | PrepBro