Python PyQt崩溃与线程安全

Python PyQt崩溃与线程安全,python,multithreading,qt,pyqt4,Python,Multithreading,Qt,Pyqt4,你好,斯塔克交换社区 首先,你们对我帮助很大,非常感谢。第一次提问: 我目前正在编写一个PyQt GUI应用程序,我发现它在windows系统上崩溃,而且它在家里的机器上给我一个segfault,而它在工作的机器上工作(都是linux mint 17)。经过一些研究,我意识到我可能已经创建了一个线程不安全的GUI,因为我有几个相互调用方法的对象 :GUI小部件只能从主线程访问,这意味着调用QApplication.exec()的线程。从任何其他线程访问GUI小部件——您对self.parent(

你好,斯塔克交换社区

首先,你们对我帮助很大,非常感谢。第一次提问:

我目前正在编写一个PyQt GUI应用程序,我发现它在windows系统上崩溃,而且它在家里的机器上给我一个segfault,而它在工作的机器上工作(都是linux mint 17)。经过一些研究,我意识到我可能已经创建了一个线程不安全的GUI,因为我有几个相互调用方法的对象

:GUI小部件只能从主线程访问,这意味着调用QApplication.exec()的线程。从任何其他线程访问GUI小部件——您对self.parent()的调用是未定义的行为,在您的情况下,这意味着崩溃

:尽管QObject是可重入的,但GUI类,尤其是QWidget及其所有子类,是不可重入的。它们只能从主线程使用。如前所述,还必须从该线程调用QCoreApplication::exec()

所以最后,我想我应该只使用信号槽系统来实现这一点

  • 这是正确的吗
  • 这仅仅是函数调用所需要的,还是我可以在运行时以线程安全的方式从其他对象操纵某些对象的字段?例如,我有一个从多个其他对象访问的选项对象,我经常从不同的源更改其中的参数。线程安全还是不安全
  • 接下来,我在示例代码中重新创建这个线程不安全行为时遇到了问题。Qt文档说明QoObject位于不同的线程中。这意味着,下面的Qt应用程序应该是线程不安全的(如果我正确获得它的话)

    但它在我的矿井上运行,在windows机器上运行也很好

  • 为什么??这是不是真的线程不安全并且可能崩溃,但事实并非如此

  • 谢谢你帮我解决这个问题……

    这是正确的吗?

    是的,您应该只使用信号槽系统来与q对象进行交互。 这就是它的本意

    这只是函数调用所需,还是我可以操纵某些对象的字段 在运行时以线程安全的方式从其他对象删除?

    我有一个可从多个其他对象访问的选项对象

    如果这里所说的对象是指Q对象:

    您的
    选项
    对象应该支持信号槽机制,您可以实现这一点 从
    QObject
    导出
    选项

    class Options(QtCore.QObject):
        optionUpdated = QtCore.pyqtSignal(object)
    
        def __init__(self):
    
            self.__options = {
                'option_1': None
            }
    
        def get_option(self, option):
            return self.__options.get(option)
    
        def set_option(self, option, value):
            self.__options[option] = value
            self.optionUpdated.emit(self)
    
    然后,所有使用此选项的小部件/对象都应该有一个连接到此信号的插槽

    一个简单的例子:

        options = Options()
        some_widget = SomeWidget()
        options.optionUpdated.connect(some_widget.options_updated)    // Is like you implement the observer pattern, right?
    
    为什么?这是否真的是线程不安全并且可能崩溃,但事实并非如此?

    线程不安全
    并不意味着“保证崩溃”,而是“这可能会崩溃”或“这很可能会崩溃”

    从pyqt API文档:

    返回对象所在的线程

    勘误表

    正如ekumoro所指出的,我已经重新检查了我之前关于每个物体在不同线程中离开的位置,并且。。。我错了

    QObject.thread
    将为每个对象返回不同的QThread实例,但
    QThread
    实际上不是线程,它只是操作系统提供的线程的包装器

    因此,代码实际上没有在不同线程中分割多个对象的问题

    为了简单起见,我对您用于演示的代码进行了一些修改:

    是的,这张照片是:

    窗口线程:
    TestWidget线程:
    TestWidget线程:
    
    演示每个控件在其自己的线程中存在


    现在,您有了信号槽机制来处理此“线程安全”,任何其他方法都不会是线程安全的。

    回答您的问题:

  • GUI小部件只能从主线程(运行的线程)访问
    QApplication.exec\(
    )。默认情况下,信号和插槽是线程安全的,因为 Qt 4

  • 任何导致从主线程以外的另一个线程进行直接Qt图形对象操作的调用都是线程安全=>将崩溃

  • 您的问题代码中没有涉及线程(线程在哪里??), 不同的QoObject存在于不同的线程中不是真的。也许你的车祸与此无关 用线


  • 作为某些注释的后续,下面是一个测试脚本,它显示了如何检查代码在哪个线程中执行:

    from PyQt4 import QtCore, QtGui
    
    class Worker(QtCore.QObject):
        threadInfo = QtCore.pyqtSignal(object, object)
    
        @QtCore.pyqtSlot()
        def emitInfo(self):
            self.threadInfo.emit(self.objectName(), QtCore.QThread.currentThreadId())
    
    class Window(QtGui.QWidget):
        def __init__(self):
            super(Window, self).__init__()
            self.button = QtGui.QPushButton('Test', self)
            layout = QtGui.QVBoxLayout(self)
            layout.addWidget(self.button)
            self.thread = QtCore.QThread(self)
            self.worker1 = Worker()
            self.worker1.setObjectName('Worker1')
            self.worker1.moveToThread(self.thread)
            self.worker1.threadInfo.connect(self.handleShowThreads)
            self.button.clicked.connect(self.worker1.emitInfo)
            self.worker2 = Worker()
            self.worker2.setObjectName('Worker2')
            self.worker2.threadInfo.connect(self.handleShowThreads)
            self.button.clicked.connect(self.worker2.emitInfo)
            self.thread.start()
    
        def handleShowThreads(self, name, identifier):
            print('Main: %s' % QtCore.QThread.currentThreadId())
            print('%s: %s\n' % (name, identifier))
    
        def closeEvent(self, event):
            self.thread.quit()
            self.thread.wait()
    
    if __name__ == '__main__':
    
        import sys
        app = QtGui.QApplication(sys.argv)
        window = Window()
        window.show()
        sys.exit(app.exec_())
    

    谢谢你的回答。但是我不理解示例代码,这一定是因为我在信号插槽系统或线程中缺少了一些东西。在代码中,外部对象通过调用其
    get\u options
    函数,从
    options
    检索值。这样,如果我的外部对象位于另一个线程中,它将执行一个据说有问题的跨线程函数调用。现在,在您的代码中,设置值后会发出信号,只需通知
    某个小部件
    选项发生了问题。是否?@grgrsr这只是一个示例,当您发出信号时,您可以只发出发生变化的选项值,而不是整个选项对象,这样您就不会执行跨线程函数调用。设置该选项后,会发出一个信号,通知所有与其连接的对象某些选项已更改。@RaydelMiranda。您的答案基本上是正确的,但您声称“每个控件都存在于自己的线程中”是完全错误的。您的示例代码所显示的只是每次调用
    self.thread()
    时都会创建一个新的
    QThread
    实例。请记住,
    QThread
    实际上不是线程!它只是一个包装器,用来管理一个最终由操作系统提供的线程。@Ekhumaro,你说得对,我对API做了更多的研究
    from PyQt4 import QtGui
    import sys
    
    class TestWidget(QtGui.QWidget):
        def __init__(self,string):
            super(TestWidget,self).__init__()
            # just to check, and yes, lives in it's own thread
            print("TestWidget thread: {}".format(self.thread()))
    
    class MainWindow(QtGui.QMainWindow):
        def __init__(self):
            super(MainWindow,self).__init__()
            print("Window thread: {}".format(self.thread()))
            Layout = QtGui.QHBoxLayout()
            for string in ['foo','bar']:
                Layout.addWidget(TestWidget(string))
            self.show()
    
    if __name__ == '__main__':
        app = QtGui.QApplication(sys.argv)
        M = MainWindow()
        sys.exit(app.exec_())
    
    Window thread: <PyQt4.QtCore.QThread object at 0x00000000025C1048>
    TestWidget thread: <PyQt4.QtCore.QThread object at 0x00000000025C4168>
    TestWidget thread: <PyQt4.QtCore.QThread object at 0x00000000025C41F8>
    
    from PyQt4 import QtCore, QtGui
    
    class Worker(QtCore.QObject):
        threadInfo = QtCore.pyqtSignal(object, object)
    
        @QtCore.pyqtSlot()
        def emitInfo(self):
            self.threadInfo.emit(self.objectName(), QtCore.QThread.currentThreadId())
    
    class Window(QtGui.QWidget):
        def __init__(self):
            super(Window, self).__init__()
            self.button = QtGui.QPushButton('Test', self)
            layout = QtGui.QVBoxLayout(self)
            layout.addWidget(self.button)
            self.thread = QtCore.QThread(self)
            self.worker1 = Worker()
            self.worker1.setObjectName('Worker1')
            self.worker1.moveToThread(self.thread)
            self.worker1.threadInfo.connect(self.handleShowThreads)
            self.button.clicked.connect(self.worker1.emitInfo)
            self.worker2 = Worker()
            self.worker2.setObjectName('Worker2')
            self.worker2.threadInfo.connect(self.handleShowThreads)
            self.button.clicked.connect(self.worker2.emitInfo)
            self.thread.start()
    
        def handleShowThreads(self, name, identifier):
            print('Main: %s' % QtCore.QThread.currentThreadId())
            print('%s: %s\n' % (name, identifier))
    
        def closeEvent(self, event):
            self.thread.quit()
            self.thread.wait()
    
    if __name__ == '__main__':
    
        import sys
        app = QtGui.QApplication(sys.argv)
        window = Window()
        window.show()
        sys.exit(app.exec_())