На этом шаге мы приведем пример использования очереди.
Рассмотрим использование очереди в многопоточном приложении на примере.
# -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import queue class MyThread(QtCore.QThread): task_done = QtCore.pyqtSignal(int, int, name = 'taskDone') def __init__(self, id, queue, parent=None): QtCore.QThread.__init__(self, parent) self.id = id self.queue = queue def run(self): while True: task = self.queue.get() # Получаем задание self.sleep(5) # Имитируем обработку self.task_done.emit(task, self.id) # Передаем данные обратно self.queue.task_done() class MyWindow(QtWidgets.QPushButton): def __init__(self): QtWidgets.QPushButton.__init__(self) self.setText("Раздать задания") self.queue = queue.Queue() # Создаем очередь self.threads = [] for i in range(1, 3): # Создаем потоки и запускаем thread = MyThread(i, self.queue) self.threads.append(thread) thread.task_done.connect(self.on_task_done, QtCore.Qt.QueuedConnection) thread.start() self.clicked.connect(self.on_add_task) def on_add_task(self): for i in range(0, 11): self.queue.put(i) # Добавляем задания в очередь def on_task_done(self, data, id): print(data, "- id =", id) # Выводим обработанные данные if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Использование модуля queue") window.resize(300, 30) window.show() sys.exit(app.exec_())
Результат работы приложения изображен на рисунке 1.
Рис.1. Результат работы приложения
В этом примере конструктор класса MyThread принимает уникальный идентификатор (id) и ссылку на очередь (queue), которые сохраняются в одноименных атрибутах класса. В методе run() внутри бесконечного цикла производится получение элемента из очереди с помощью метода get(). Если очередь пуста, поток будет ожидать, пока не появится хотя бы один элемент. Далее производится обработка задания (в нашем случае - просто задержка), а затем обработанные данные передаются главному потоку через сигнал taskDone, принимающий два целочисленных параметра. В следующей инструкции с помощью метода task_done() указывается, что задание было обработано.
Отметим, что здесь в вызове функции pyqtSignal() присутствует именованный параметр name:
task_done = QtCore.pyqtSignal(int, int, name = 'taskDone')
Он задает имя сигнала и может быть полезен в том случае, если это имя отличается от имени атрибута класса, соответствующего сигналу, - как в нашем случае, где имя сигнала: taskDone, отличается от имени атрибута: task_done. После чего мы можем обращаться к сигналу как по имени соответствующего ему атрибута:
self.task_done.emit(task, self.id)
self.taskDone.emit(task, self.id)
Главный поток реализуется с помощью класса MyWindow. Обратите внимание, что наследуется класс QPushButton (кнопка), а не класс QWidget. Все визуальные компоненты являются наследниками класса QWidget, поэтому любой компонент, не имеющий родителя, обладает своим собственным окном. В нашем случае используется только кнопка, поэтому можно сразу наследовать класс QPushButton.
Внутри конструктора класса MyWindow с помощью метода setText() задается текст надписи на кнопке, затем создается экземпляр класса Queue и сохраняется в атрибуте queue. В следующем выражении производится создание списка, в котором будут храниться ссылки на объекты потоков. Сами объекты потоков (в нашем случае их два) создаются внутри цикла и добавляются в список. Внутри цикла производится также назначение обработчика сигнала taskDone и запуск потока с помощью метода start(). Далее назначается обработчик нажатия кнопки.
При нажатии кнопки Раздать задания вызывается метод on_add_task(), внутри которого производится добавление заданий в очередь. После этого потоки выходят из цикла ожидания, и каждый из них получает одно уникальное задание. После окончания обработки потоки генерируют сигнал taskDone и вызывают метод task_done(), информирующий об окончании обработки задания. Главный поток получает сигнал и вызывает метод on_task_ done(), внутри которого через параметры будут доступны обработанные данные. Так как метод расположен в GUI-потоке, мы можем изменять свойства компонентов и, например, добавить результат в список или таблицу. В нашем же примере результат просто выводится в окно консоли (чтобы увидеть сообщения, следует сохранить файл с расширением ру, а не pyw). После окончания обработки задания потоки снова получают задания. Если очередь окажется пуста, потоки перейдут в режим ожидания заданий.
На следующем шаге мы рассмотрим классы QMutex и QMutexLocker.