В предыдущей статье вы могли узнать что такое очередь в целом и как работает FIFO-очередь asyncio.Queue. Давайте продолжим и посмотрим на примере библиотеки aiohttp как работают очереди с приоритетом asyncio.PriorityQueue. Автор предыдущей статьи про asyncio.Queue довольно подробно описал тему про FIFO очередь, но дальше, про остальные типы очередей, пишет довольно шаблонно, примеры в статье мне тоже не понравились и я решил продолжить тему частичным переводом другой статьи. Если вы нашли ошибку, пожалуйста, используйте Ctrl+Enter и я исправлю. Спасибо! Повторим в чем смысл паттерна producer-consumer(производитель-потребитель). Представьте себе два типа задач, разделяющих очередь. Задача A производит данные и помещает их в очередь, а задача B извлекает данные из очереди для обработки. Это и есть модель "производитель-потребитель", где задача A - производитель, а задача B - потребитель. По аналогии с супермаркетом, покупатели являются производителями, кассиры - потребителями, а очередь покупателей представляет собой очередь.  В высококонкурентных программах производители часто генерируют данные быстро, а потребители обрабатывают их медленно. Таким образом, производители должны дождаться окончания обработки данных потребителями, прежде чем продолжать генерировать данные. Иногда потребители обрабатывают данные быстро, а производители — медленно. Это приводит к тому, что потребители ждут, пока производители сгенерируют данные, прежде чем продолжить работу. Для баланса между производителями и потребителями необходима очередь, в которой хранятся данные, произведенные производителем. Очередь выполняет роль буфера и разделяет производителей и потребителей. В этой статье мы обсудили как работает очередь типа FIFO в asyncio. Теперь давайте посмотрим как работают очереди с приоритетом asyncio.PriorityQueue. Определение из wikipedia: Очередь с приоритетом (англ. priority queue) — абстрактный тип данных в программировании, поддерживающий две обязательные операции — добавить элемент и извлечь максимум[1] (минимум). Предполагается, что для каждого элемента можно вычислить его приоритет — действительное число или в общем случае элемент линейно упорядоченного множества. Предположим, имеется очередь, в которой стоят задачи, каждая из которых требует длительного времени обработки. Журнал ошибок или VIP-доступ пользователя - это высокоприоритетная задача, требующая немедленного внимания. Что же делать? Именно здесь на помощь приходит asyncio.PriorityQueue. В отличие от очередей FIFO, основанных на списках, asyncio.PriorityQueue основана на кучах. Она построена с использованием структуры бинарного дерева. Вы возможно знакомы с двоичными деревьями поиска, которые гарантируют, что самый младший узел всегда является крайним левым узлом. Однако двоичное дерево в asyncio.PriorityQueue гарантирует, что самый младший узел всегда находится наверху, поэтому узел с наивысшим приоритетом постоянно удаляется первым. Методы для очередей с приоритетом asyncio.PriorityQueue такие же, как для asyncio.Queue. Подробнее с методами вы можете ознакомиться в этой статье await queue.put(item) - для помещения элемента в очередь. Если очередь переполнена, этот метод будет ждать, пока не освободится свободный слот. await queue.get() - для получения элемента из очереди. Если очередь пуста, этот метод будет ждать, пока элемент не станет доступен. queue.task_done() - для индикации того, что ранее полученный элемент был обработан. Этот метод должен вызываться потребительскими корутинами после завершения работы с элементом. await queue.join() - для блокировки обработки всех элементов в очереди. Этот метод должен вызываться корутинами-производителями после того, как они завершат занесение элементов в очередь. Можно также использовать некоторые "некорутинные" методы очереди, например: queue.put_nowait(item) - чтобы поместить элемент в очередь без блокировки. Если очередь переполнена, этот метод вызовет исключение QueueFull. queue.get_nowait() - для получения элемента из очереди без блокировки. Если очередь пуста, то этот метод вызовет исключение QueueEmpty. queue.qsize() - для получения количества элементов в очереди. queue.empty() - для проверки, пуста ли очередь. queue.full() - для проверки заполнения очереди.  Проиллюстрируем использование asyncio.PriorityQueue на примере реального сценария. Представьте, что у нас есть API сервиса заказов. API требует времени на обработку каждого заказа, но мы не можем заставлять пользователей ждать слишком долго. Поэтому, когда пользователь размещает заказ, API сначала помещает его в очередь, позволяя фоновой задаче обработать его асинхронно и сразу же вернуть сообщение пользователю. Данный API принимает заказы от двух типов пользователей: обычных и VIP. При этом необходимо следить за тем, чтобы заказы VIP-пользователей обрабатывались с наивысшим приоритетом. Давай рассмотрим реализацию программы с приоритетной очередью на примере aiohttp: Сначала мы определяем Enum(перечисление), обозначающее две категории: обычные пользователи и VIP-пользователи. Затем с помощью dataclass определяем заказ пользователя, который содержит тип пользователя и продолжительность обработки заказа. Продолжительность обработки заказа не учитывается при сортировке приоритетов.  Дальше мы определяем метод-потребитель process_order_worker, который получает заказы из очереди и имитирует их обработку. Не забудьте использовать queue.task_done(), чтобы сообщить очереди, что мы закончили обработку заказа. После этого мы реализуем API заказа, используя aiohttp. Этот API отвечает на запросы пользователей, генерирует объект заказа и помещает его в asyncio.PriorityQueue. Затем он немедленно возвращает ответ пользователю, что позволяет избежать времени ожидания. При запуске программы мы используем команду create_order_queue для инициализации очереди и упорядочивания задач потребления. При завершении работы программы мы используем команду destroy_order_queue, чтобы убедиться, что все заказы в очереди обработаны и фоновые задачи корректно закрыты. Функция queue.join() будет ожидать обработки всех данных в очереди. asyncio.wait_for устанавливает таймаут в 20 секунд, по истечении которого она больше не будет ожидать завершения работы queue.join().  Мы можем протестировать эту реализацию, используя HTTP-запрос PyCharm (только в Pro версии):  Или, например, так, если у вас community версия: Как видите, две высокоприоритетные задачи обрабатываются, как и ожидалось. Отлично! В этой статье мы повторили для чего нужен паттерн производитель-потребитель: Балансировка между производителями и потребителями, максимизация использования ресурсов. Развязывание системы, позволяющее производителям и потребителям масштабироваться независимо друг от друга. Также, на реальном примере увидели, как использовать asyncio.PriorityQueue для решения ситуаций, когда задачам требуется расстановка приоритетов. Асинхронное программирование на Python - мощный инструмент, а паттерн производитель-потребитель с asyncio.Queue - это универсальный подход к обработке параллелизма и расстановке приоритетов в ваших приложениях. Пожалуйста, подпишитесь, если вы нашли статьи полезными, и получайте новые истории на свой почтовый ящик. Если у вас есть вопросы, вы можете найти меня на LinkedIn или в Twitter(X).  Ищу работу