🐍🔀 Под капотом asyncio: принципы работы и ключевые концепции
Библиотека asyncio предоставляет полный набор инструментов для организации параллельного выполнения кода в Python с использованием концепции асинхронности. Но как на самом деле работает asyncio? Давайте разберемся в ключевых принципах и понятиях.
Asyncio – библиотека, которая предоставляет инфраструктуру для написания параллельного кода с использованием концепции асинхронного программирования. Она позволяет эффективно обрабатывать многочисленные задачи ввода-вывода (например, сетевые операции или чтение/запись из файлов) без необходимости создавать множество потоков или процессов. Ключевые концепции asyncio:
Событийный цикл (Event Loop)
- Это ядро asyncio, которое отвечает за планирование и выполнение задач (корутин).
- Реализован на языке C для максимальной эффективности.
- Работает по принципу однопоточной многозадачности (single-threaded concurrency).
- Постоянно опрашивает очередь событий и выполняет соответствующие задачи.
Корутины (Coroutines)
- Это специальные функции, выполнение которых можно приостанавливать на определенных точках с помощью ключевого слова
await
. - Используются для написания асинхронного, событийно-ориентированного кода.
- Могут ждать асинхронные события (ввод-вывод, сигналы или другие корутины).
- Определяются с помощью
async def
.
Задачи (Tasks)
- Представляют собой обертки над корутинами для их планирования и выполнения.
- Создаются с помощью
asyncio.create_task(coroutine)
илиasyncio.ensure_future(coroutine)
. - Задачи можно отменять, объединять с другими задачами и отслеживать на предмет исключений.
- Задачи добавляются в событийный цикл и выполняются в порядке их готовности.
Будущие объекты (Futures)
- Представляют результат асинхронной операции, который может быть доступен позже.
- Задачи наследуются от класса
Future
и добавляют собственные методы управления. - Могут быть использованы для координации между разными частями программы.
Транспорты и протоколы
- Транспорты (sockets) отвечают за передачу данных по сети.
- Протоколы определяют, как интерпретировать эти данные (HTTP, WebSocket и др.).
- asyncio предоставляет высокоуровневые абстракции для работы с транспортами и протоколами.
Другие инструменты
- Пулы для управления ограниченными ресурсами (потоки, подпроцессы).
- Синхронизаторы для координации между корутинами (Lock, Event, Condition).
- Очереди для безопасной передачи данных между корутинами.
- Сигналы для обработки внешних событий (UNIX-сигналы).
Вот как работает asyncio:
- Создается событийный цикл, который будет управлять выполнением задач.
- Определяются корутины, которые выполняют некоторую работу и могут приостанавливаться на операциях ввода-вывода с помощью ключевого слова
await
. - Корутины оборачиваются в задачи с помощью функции
asyncio.create_task()
. - Задачи планируются для выполнения в событийном цикле с помощью функции
asyncio.run()
или добавляются в цикл вручную. - Событийный цикл начинает выполнять задачи по очереди. Когда задача достигает операции ввода-вывода (например,
await asyncio.sleep()
), она приостанавливается, и событийный цикл переключается на другую задачу. - Когда операция ввода-вывода завершается, задача возобновляется и продолжает выполнение до следующей операции ввода-вывода или завершения.
Так asyncio позволяет эффективно использовать один поток для выполнения множества задач с операциями ввода-вывода, избегая блокировки и простоев из-за ожидания завершения этих операций.
Важно отметить, что asyncio оптимален именно для I/O-bound задач (сеть, файловые операции и т. д.), и не подходит для CPU-bound операций (вычислительно-интенсивных задач), поскольку в этом случае он не сможет переключаться на другие задачи во время блокировки. Для такого рода задач лучше использовать многопоточность или многопроцессорность. Подробнее о принципах работы asyncio – в этой статье и в этом видео.