Шаг 19.
Библиотека PyQt5.
Знакомство с PyQt5. Многопоточные приложения. Класс QMutex

    На этом шаге мы рассмотрим использование этого класса.

    Как вы уже знаете, совместное использование одного ресурса сразу несколькими потоками может привести к непредсказуемому поведению программы или даже аварийному ее завершению. То есть, доступ к ресурсу в один момент времени должен иметь лишь один поток. Следовательно, внутри программы необходимо предусмотреть возможность блокировки ресурса одним потоком и ожидание его разблокировки другим потоком.

    Реализовать блокировку ресурса в PyQt позволяют классы QMutex и QMutexLocker из модуля QtCore.

    Конструктор класса QMutex создает так называемый мьютекс и имеет следующий формат:

  <Объект> = QMutex([mode=QtCore.QMutex.NonRecursive])

    Необязательный параметр mode может принимать значения NonRecursive (поток может запросить блокировку только единожды, а после снятия блокировка может быть запрошена снова, - значение по умолчанию) и Recursive (поток может запросить блокировку несколько раз, и чтобы полностью снять блокировку, следует вызвать метод unlock() соответствующее количество раз).

    Класс QMutex поддерживает следующие методы:

    Рассмотрим использование класса QMutex на примере.

#  -*- coding:   utf-8  -*-
from PyQt5 import QtCore, QtWidgets

class MyThread(QtCore.QThread):
    x = 10	# Атрибут класса
    mutex = QtCore.QMutex ()	# Мьютекс
    def __init__(self, id, parent=None):
        QtCore.QThread.__init__(self, parent)
        self.id = id
    def run(self):
        self.change_x()
    def change_x(self):
        MyThread.mutex.lock()	# Блокируем
        print("x =", MyThread.x, "id =", self.id)
        MyThread.x += 5
        self.sleep(2)
        print("x =", MyThread.x, "id =", self.id)
        MyThread.x += 34
        print("x =", MyThread.x, "id =", self.id)
        MyThread.mutex.unlock() # Снимаем блокировку

class MyWindow(QtWidgets.QPushButton):
    def __init__ (self):
        QtWidgets.QPushButton.__init__ (self)
        self.setText("Запустить")
        self.thread1 = MyThread(1)
        self.thread2 = MyThread(2)
        self.clicked.connect(self.on_start)
    def on_start (self):
        if not self.thread1.isRunning():
            self.thread1.start()
        if not self.thread2.isRunning():
            self.thread2.start()
 
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.setWindowTitle("Использование класса QMutex")
    window.resize(300, 30)
    window.show()
    sys.exit(app.exec_())
Архив с файлом можно взять здесь.

    В этом примере внутри класса MyThread мы создали атрибут х, который доступен всем экземплярам класса. Изменение значения атрибута в одном потоке повлечет изменение значения и в другом потоке. Если потоки будут изменять значение одновременно, то предсказать текущее значение атрибута становится невозможным. Следовательно, изменять значение можно только после установки блокировки.

    Чтобы обеспечить блокировку, внутри класса MyThread создается экземпляр класса QMutex и сохраняется в атрибуте mutex. Обратите внимание, что сохранение производится в атрибуте класса, а не в атрибуте экземпляра класса. Чтобы блокировка сработала, необходимо, чтобы защищаемый атрибут и мьютекс находились в одной области видимости. Далее весь код метода change_x(), в котором производится изменение атрибута х, помещается между вызовами методов lock() и unlock() мьютекса, - таким образом гарантируется, что он будет выполнен сначала одним потоком и только потом - другим.

    Внутри конструктора класса MyWindow производится создание двух объектов класса MyThread и назначение обработчика нажатия кнопки. По нажатию кнопки Запустить будет вызван метод on_start(), внутри которого производится запуск сразу двух потоков одновременно, - при условии, что потоки не были запущены ранее. В результате мы получим в окне консоли следующий результат (рисунок 1):


Рис.1. Результат работы приложения

    Как можно видеть, сначала изменение атрибута произвел поток с идентификатором 1, a лишь затем - поток с идентификатором 2. Если блокировку не указать, то результат будет иным:

x = 10   id = 1
x = 10   id = 2
x = 20   id = 1
x = 20   id = 2
x = 54   id = 1
x = 88   id = 2
x = 132   id = 1
x = 166   id = 2

    В этом случае поток с идентификатором 2 изменил значение атрибута х до окончания выполнения метода changes() в потоке с идентификатором 1.

    На следующем шаге мы закончим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг