Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs
На прошлом
занятии мы с вами подробно разобрали работу класса queue.Queue, реализующий потокобезопасную
очередь типа FIFO. Продолжим эту
тему и рассмотрим еще два аналогичных класса:
- queue.LifoQueue – потокобезопасная
реализация очереди типа LIFO;
- queue.PriorityQueue – потокобезопасная
реализация очереди с приоритетами.
Очередь queue.LifoQueue типа LIFO
Класс LifoQueue работает по
аналогии с классом Queue с тем же набором методов, отличаясь
лишь порядком извлечения элементов: они выбираются в обратном порядке, то есть,
последний добавленный удаляется первым. Простая визуализация этого типа очереди
демонстрирует стопка тарелок, когда новая кладется сверху и берется тоже
сверху:
Это принцип
добавления/удаления элементов также известен под названием стек (stack).
Давайте для
примера реализуем стек с помощью класса LifoQueue. Вначале нужно
создать объект этой очереди:
import queue
stack = queue.LifoQueue() # LIFO без ограничений длины
Затем, добавим
несколько элементов в этот стек так, как это мы делали на прошлом занятии:
data = ['one', 2, 'three', 4, 5, 6]
for d in data:
try:
stack.put(d, block=False) # в многопоточных приложениях block=True
except queue.Full as e:
print("Full: " + str(e))
break
print("Число элементов в очереди:", stack.qsize())
И извлечем
добавленные элементы:
while not stack.empty():
try:
d = stack.get(block=False) # в многопоточных приложениях block=True
print(d)
except queue.Empty as e:
print("Empty: " + str(e))
break
Обратите
внимание, что данные извлекаются в обратном порядке: сначала значение 6, потом
5 и последним ‘one’.
Очередь queue.PriorityQueue
Рассматриваемые
до сих пор очереди имели жесткий приоритет (порядок) добавления/извлечения
элементов. Однако на практике существуют задачи, когда элементы следует
извлекать согласно их приоритетам. Причем, чем ниже значение, указанное в
приоритете, тем раньше элемент должен быть извлечен из очереди.
Примерами
классических задач, где имеет смысл использовать очереди с приоритетами,
являются следующие:
- Планировщик
заданий с приоритетами, где каждому заданию назначается определенный уровень
важности (приоритет). Необходимо обеспечить выполнение наиболее важных задач
первыми.
- Обработка
заказов в службе доставки. Высокоприоритетные заказы должны доставляться
быстрее низкоприоритетных.
- Графический
интерфейс с отложенными событиями. Здесь разные события имеют разную важность.
Например, обновление интерфейса должно происходить чаще, чем обработка фоновых
процессов.
- Оптимизация серверных
ресурсов. Допустим, сервер обрабатывает запросы пользователей. Запросы делятся
на две категории: критические и обычные. Важно сначала обработать критически
важные запросы.
Работа с
очередью PriorityQueue выполняется так
же, как и с двумя другими классами: Queue и LifoQueue. Единственное
ключевое отличие, что метод put() ожидает объект, который можно
сравнивать на меньше/больше. В самом простом варианте можно использовать кортеж
в формате:
(приоритет,
данные)
Например, создадим
очередь со следующими данными:
import queue
tasks = queue.PriorityQueue()
lst_t = [(1, 'варим пельмешки'), (7, 'программируем'), (2, 'едим пельмешки'),
(2, 'пьем кофе'), (4, 'принимаем лабы'), (3, 'читаем лекции')]
for d in lst_t:
tasks.put(d)
print("Число элементов в очереди:", tasks.qsize())
Каждый элемент
(задача) имеет свой числовой приоритет, причем приоритеты могут совпадать. В
этом случае они будут извлекаться по порядку их записи в очередь.
Извлечем
элементы из этой очереди и посмотрим на порядок их следования:
while not tasks.empty():
try:
d = tasks.get(block=False) # в многопоточных приложениях block=True
print(d)
except queue.Empty as e:
print("Empty: " + str(e))
break
Увидим, что
элементы с наименьшим значением приоритета выбираются в первую очередь:
Число
элементов в очереди: 6
(1,
'варим пельмешки')
(2,
'едим пельмешки')
(2,
'пьем кофе')
(3,
'читаем лекции')
(4,
'принимаем лабы')
(7,
'программируем')
Пользовательский класс для объектов очереди queue.PriorityQueue
Конечно, кортеж
удобен для простых реализаций элементов очереди с приоритетом. Однако на
практике не редко для этих целей объявляют свой собственный класс, в котором
определяют операции сравнения: меньше, больше. Например, это можно сделать так:
class Task:
def __init__(self, data, priority):
self.data = data
self.priority = priority
def __lt__(self, other):
return self.priority < other.priority
def __repr__(self):
return f"Task: {self.data}, {self.priority}"
Обратите
внимание, в этом классе определен магический метод __lt__, который
вызывается для операции сравнения на меньше. Соответственно, он возвращает
булево значение при сравнении соответствующих приоритетов двух объектов класса Task. Например:
a = Task('a', 1)
b = Task('b', 2)
a < b # True
И, кроме того,
автоматически реализуется метод сравнения на больше, как отрицание результата
метода __lt__:
Те из вас, кто
изучал курс по ООП Python, все это прекрасно уже знают.
Итак, после
объявления класса, можно создать список из этих объектов и добавить их в
очередь:
tasks = queue.PriorityQueue()
lst_t = [Task('варим пельмешки', 1), Task('программируем', 7), Task('едим пельмешки', 2),
Task('пьем кофе', 2), Task('принимаем лабы', 4), Task('читаем лекции', 3)]
for d in lst_t:
tasks.put(d)
Программа здесь
выглядит абсолютно также, только вместо кортежей появляются объекты класса Task. После выбора
этих объектов из очереди, получим результат:
Число
элементов в очереди: 6
Task:
варим пельмешки, 1
Task:
едим пельмешки, 2
Task:
пьем кофе, 2
Task:
читаем лекции, 3
Task:
принимаем лабы, 4
Task:
программируем, 7
Как видите, все
работает прежним образом, элементы выбираются в порядке указанных приоритетов,
начиная с наименьшего.
Таким образом,
класс PriorityQueue хорошо подходит для ситуаций, когда важно обрабатывать
события или задачи в строго определенном порядке приоритетов. Это особенно
полезно в потоковых сценариях и многозадачных системах, где важно гарантировать
соблюдение строгого порядка обработки запросов.
Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs