无法使用 QMutex 锁定变量

Posted

技术标签:

【中文标题】无法使用 QMutex 锁定变量【英文标题】:Can't lock variable with QMutex 【发布时间】:2021-01-13 07:44:56 【问题描述】:

我是多线程的新手。我有一个 PySide2 应用程序。它有 3 个不同的线程。第一个是运行 GUI 的主线程,后者负责摄像头流,第三个负责提供 API 以从其他点访问 GUI 的 Flask。 (根据文章从不同线程访问 GUI 不是一个好主意:https://realpython.com/python-pyqt-qthread/#reusing-threads-qrunnable-and-qthreadpool)。

当请求到达端点时,我想在 GUI 上获取一些值并返回它们。我试图实现信号和槽机制来执行它。但是,我的端点在从 GUI 获取变量之前返回变量的默认值。负责端点的线程不会等待 GUI 线程设置将返回的值。我尝试以不同的方式使用 QMutex 锁定变量,以首先通过 GUI 设置值,但我无法成功。设置值后如何锁定变量并返回?

在线程上运行的 Flask 类:

from PySide2.QtCore import Signal, QObject, Slot, QMutex
from flask import Flask

from SharedData import SharedData

class API(QObject):
    signal_get_value = Signal()

    def __init__(self):
        super(API, self).__init__()
        self.app = Flask(__name__)
        self.app.add_url_rule('/get_value/', 'get_value', self.get_value)
        self.data = SharedData()


    def start(self):
        self.app.run()

    def get_value(self):
        self.signal_get_value.emit()
        measurement = self.data.get_measurement()
        data = "measurement": measurement

        return data

SharedData 类:

class SharedData:
    __measurement = None

    @classmethod
    def get_measurement(cls):
        return cls.__measurement

    @classmethod
    def set_measurement(cls, measurement):
        cls.__measurement = measurement

在我的 GUI 模块中改变值的方法:

mutex = QMutex()

    def read_measurement(self):
        self.mutex.lock()
        
        #some processes and the output assign to the 'result' variable
        
        self.api.data.set_measurement(result)
        self.mutex.unlock()

编辑

我正在添加一个代码来澄清它。您可以在其中找到read_measurement 方法。其余代码相同。 @eyllanesc

import sys

from PySide2.QtCore import QThread, QMutex
from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QHBoxLayout, QLabel

from API import API

class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.init_UI()


    def init_UI(self):
        self.setFixedSize(690, 530)
        self.image_lbl = QLabel()

        self.image_lbl.setPixmap(QPixmap("img/im.jpg"))

        btn_cnt = QPushButton("Continue")
        btn_pa = QPushButton("Pause")

        hbox = QHBoxLayout()
        hbox.addWidget(btn_cnt)
        hbox.addWidget(btn_pa)

        vbox = QVBoxLayout()
        vbox.addWidget(self.image_lbl)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

        self.start_api()
        self.show()


    def start_api(self):
        self.thread_API = QThread()
        self.api = API()
        self.api.moveToThread(self.thread_API)
        self.thread_API.started.connect(self.api.start)
        self.api.signal_get_value.connect(self.read_measurement)

        self.thread_API.start()

    dv = 0
    mutex = QMutex()

    def read_measurement(self):
        self.mutex.lock()

        measurement = None
        result = "measurement " + str(self.dv)
        self.dv += 1
        self.api.data.set_measurement(result)

        self.mutex.unlock()



if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_form = MainWindow()
    sys.exit(app.exec_())

【问题讨论】:

请提供minimal reproducible example 我添加了示例。 【参考方案1】:

解决问题的方法是暂停 API(工作)线程。我仍然想知道我可以使用互斥锁、信号量等。

添加了 sleep 方法来暂停线程。

from time import sleep

from PySide2.QtCore import Signal, QObject, Slot, QMutex
from flask import Flask

from SharedData import SharedData

class API(QObject):
    signal_get_value = Signal()

    def __init__(self):
        super(API, self).__init__()
        self.__app = Flask(__name__)
        self.__app.add_url_rule('/get_value/', 'get_value', self.get_value)
        self.data = SharedData()
        self.pause = True


    def start(self):
        self.__app.run()

    def __sleep_thread(self):
        """method pauses the thread to enable other threads to get the request done 

        Returns
        -------
        None
        """

        while self.pause:
            sleep(0.05)

        self.pause = True


    def get_value(self):
        self.signal_get_value.emit()
        self.__sleep_thread()

        measurement = self.data.get_measurement()
        data = "measurement": measurement

        return data

GUI 模块。

import sys

from PySide2.QtCore import QThread, QMutex
from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import QApplication, QVBoxLayout, QWidget, QPushButton, QHBoxLayout, QLabel

from API import API

class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.init_UI()


    def init_UI(self):
        self.setFixedSize(690, 530)
        self.image_lbl = QLabel()

        self.image_lbl.setPixmap(QPixmap("img/im.jpg"))

        btn_cnt = QPushButton("Continue")
        btn_pa = QPushButton("Pause")

        hbox = QHBoxLayout()
        hbox.addWidget(btn_cnt)
        hbox.addWidget(btn_pa)

        vbox = QVBoxLayout()
        vbox.addWidget(self.image_lbl)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

        self.start_api()
        self.show()


    def start_api(self):
        self.thread_API = QThread()
        self.api = API()
        self.api.moveToThread(self.thread_API)
        self.thread_API.started.connect(self.api.start)
        self.api.signal_get_value.connect(self.read_measurement)

        self.thread_API.start()
        # ToDo: Do I need the terminate thread when exited from the app?

    dv = 0
    def read_measurement(self):
        measurement = None
        result = "measurement " + str(self.dv)
        self.dv += 1
        self.api.data.set_measurement(result)
        self.api.pause = False



if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_form = MainWindow()
    sys.exit(app.exec_())

【讨论】:

以上是关于无法使用 QMutex 锁定变量的主要内容,如果未能解决你的问题,请参考以下文章

你怎么知道 QMutex 是不是被锁定?

QT QMutex简介(QT多线程编程一)

为啥互斥锁没有被锁定

QT——QMutex(互斥量)

Qt系列文章之三十一 (基于QThread互斥量的线程同步线程)

Qt系列文章之三十一 (基于QThread互斥量的线程同步线程)