Как работает asyncio для асинхронного кода?python-37

Asyncio — это библиотека Python для написания конкурентного (concurrent) кода с использованием синтаксиса async/await. В отличие от многопоточности, asyncio использует однопоточную модель с кооперативной многозадачностью (cooperative multitasking).

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

1. Event Loop

Сердце asyncio — это цикл событий, который:

  • Управляет выполнением корутин (coroutines)
  • Обрабатывает системные события
  • Выполняет операции ввода-вывода
import asyncio

async def main():
    print('Hello')
    await asyncio.sleep(1)
    print('World')

asyncio.run(main())  # Запускает event loop

2. Корутины

Функции, объявленные через async def, которые могут приостанавливать выполнение через await:

async def fetch_data():
    print('Fetching data...')
    await asyncio.sleep(2)  # Неблокирующая задержка
    return {'data': 123}

3. Задачи

Обертки вокруг корутин, которые планируют их выполнение в event loop:

async def main():
    task = asyncio.create_task(fetch_data())
    print('Doing other work...')
    data = await task
    print(data)

Ключевые компоненты

1. Awaitables

Три типа объектов можно использовать с await:

  1. Корутины (async def функции)
  2. Задачи (asyncio.Task)
  3. Фьючи (asyncio.Future)

2. Основные функции

# Запуск нескольких задач параллельно
async def main():
    task1 = asyncio.create_task(operation1())
    task2 = asyncio.create_task(operation2())
    await asyncio.gather(task1, task2)

3. Синхронизация

Asyncio предоставляет примитивы для синхронизации:

  • Lock (блокировка)
  • Event (событие)
  • Semaphore (семафор)
async def worker(lock, id):
    async with lock:  # Асинхронный контекстный менеджер
        print(f'Worker {id} acquired lock')
        await asyncio.sleep(1)

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

1. Асинхронные HTTP-запросы

import aiohttp

async def fetch_url(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

2. Работа с базой данных

async def get_user(db, user_id):
    await db.connect()
    user = await db.fetch('SELECT * FROM users WHERE id = $1', user_id)
    return user

Внутреннее устройство

1. Генераторы и await

Корутины построены на генераторах. Ключевое слово await аналогично yield from в генераторах.

2. Callback-под капотом

Asyncio использует колбэки, но скрывает их за синтаксисом async/await.

3. Selector

Использует системные вызовы select/epoll/kqueue для эффективного ожидания I/O.

Ограничения

  1. CPU-bound задачи: asyncio не подходит для CPU-intensive операций
  2. Блокирующий код: любой блокирующий вызов остановит весь event loop
  3. Кривая обучения: требует переосмысления подхода к программированию

Резюмируем

Asyncio идеально подходит для:

  • Высоконагруженных I/O приложений
  • Веб-серверов и API
  • Работы с сетевыми протоколами
  • Микросервисных архитектур

Пример с подсветкой (VSCode style):

import asyncio
from typing import List

async def process_item(item: str) -> str:
    await asyncio.sleep(0.1)  # Имитация I/O операции
    return item.upper()

async def process_all(items: List[str]) -> List[str]:
    tasks = [process_item(item) for item in items]
    return await asyncio.gather(*tasks)

async def main():
    results = await process_all(['a', 'b', 'c'])
    print(results)  # ['A', 'B', 'C']

asyncio.run(main())