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

Как устроено асинхронное программирование?

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

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

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

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

# Асинхронное программирование в Python

Основные концепции

Асинхронное программирование — это парадигма, позволяющая программе выполнять операции без блокирования основного потока выполнения. В Python это реализовано через модуль asyncio, который предоставляет инструменты для написания асинхронного кода на основе корутин (coroutines) и событийного цикла (event loop).

Отличие от многопоточности: если потоки требуют синхронизации и могут вызвать race conditions, то асинхронный код выполняется в одном потоке, переключаясь между задачами в точках ожидания (await).

Архитектура asyncio

1. Event Loop (цикл событий)

Это сердце асинхронной программы. Event loop отслеживает задачи и переключается между ними:

import asyncio

# Получить текущий event loop
loop = asyncio.get_event_loop()

# Запустить корутину
result = loop.run_until_complete(some_coroutine())

# Или через высокоуровневый API (Python 3.7+)
result = asyncio.run(some_coroutine())

2. Корутины (Coroutines)

Функция, определённая с ключевым словом async, это корутина:

async def fetch_data(url):
    # await используется для ожидания асинхронной операции
    response = await some_async_request(url)
    return response

Корутина может быть приостановлена (await) и возобновлена позже, освобождая поток для других задач.

3. Tasks и Futures

Task — это обёртка над корутиной, которая спланирована в event loop:

async def main():
    # Создать задачу (запустить без await)
    task = asyncio.create_task(fetch_data('url1'))
    
    # Или старый способ
    task = asyncio.ensure_future(fetch_data('url2'))
    
    # Дождаться результата
    result = await task

Future — это объект, представляющий результат операции, которая произойдёт в будущем.

Практические примеры

Параллельное выполнение операций

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        # Создаём задачи для всех URL
        tasks = [fetch(session, url) for url in urls]
        
        # asyncio.gather() выполняет все задачи параллельно
        results = await asyncio.gather(*tasks)
        return results

# Запуск
urls = ['http://example.com', 'http://example.org']
results = asyncio.run(fetch_all(urls))

Таймауты и отмена задач

async def long_running_task():
    try:
        await asyncio.sleep(100)
    except asyncio.CancelledError:
        print('Задача отменена')
        raise

async def main():
    task = asyncio.create_task(long_running_task())
    
    try:
        # Установить таймаут 5 секунд
        await asyncio.wait_for(task, timeout=5)
    except asyncio.TimeoutError:
        print('Превышено время ожидания')
        task.cancel()

Использование asyncio.Queue

async def producer(queue):
    for i in range(5):
        await queue.put(i)
        await asyncio.sleep(1)

async def consumer(queue):
    while True:
        item = await queue.get()
        print(f'Обработан элемент: {item}')
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    await asyncio.gather(
        producer(queue),
        consumer(queue)
    )

asyncio.run(main())

Event Loop: как это работает

  1. Инициализация: создаётся event loop
  2. Регистрация: корутины регистрируются как tasks
  3. Цикл: event loop выполняет задачи по одной
  4. Переключение: при await задача приостанавливается, event loop выбирает следующую
  5. Результат: готовый результат возвращается

Это похоже на кооперативную многозадачность — задача сама решает, когда отпустить управление.

Сравнение: sync vs async

# Синхронный код (блокирующий)
def fetch_sync(urls):
    results = []
    for url in urls:  # Последовательно
        response = requests.get(url)  # Ждём каждый запрос
        results.append(response.text)
    return results

# Асинхронный код (неблокирующий)
async def fetch_async(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]  # Все параллельно
        results = await asyncio.gather(*tasks)  # Дождаться всех
    return results

Для 10 URL'ов с задержкой 1 сек:

  • Синхронный: ~10 сек
  • Асинхронный: ~1 сек

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

Подходит для:

  • I/O операции (HTTP запросы, БД, файлы)
  • Веб-скреперы и сетевые приложения
  • Чат-боты и WebSocket соединения
  • Долгоживущие приложения (веб-серверы)

Не подходит для:

  • Тяжёлые вычисления (используй многопроцессность)
  • Простые скрипты без ожиданий

Best Practices

  • Используй asyncio.run() вместо loop.run_until_complete()
  • Избегай блокирующих операций внутри async функций (используй aiohttp, asyncpg вместо requests, psycopg2)
  • Правильно обрабатывай исключения в корутинах
  • Используй asyncio.gather() вместо ручного управления задачами
  • Документируй асинхронность в docstring'ах

Асинхронное программирование — это мощный инструмент для создания быстрых и отзывчивых приложений на Python.