Search code examples
pythonpython-3.xpyqt5signals-slotsqthread

Do signals need to be protected by a QMutex?


I am working on a GUI with PyQt5 and 2 QThreads that would use each their own signals plus a shared signal used to send error code back to the GUI. I know that QThreads are meant to be used with pyqtSignals, but are their behaviour with signals excepting you to emit a shared signal from two different threads? Also, would using a Qmutex on the shared signal ensure that the threads access it at the same time or is it useless when dealing with signals?

I wrote this sample code, which runs properly, but I am not sure to understand how the signals are dealt with:

from PyQt5.QtCore import QObject, pyqtSignal, QThread, QCoreApplication
import time
import sys


class Class2(QThread):
    def __init__(self, signal):
        super().__init__()
        self.signal2 = signal

    def run(self):
        self.signal2.emit("Class 2 signal emitted")


class Class1(QThread):
    def __init__(self, signal):
        super().__init__()
        self.signal1 = signal

    def run(self):
        self.signal1.emit("Class 1 signal emitted")


class Action(QObject):
    shared_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        class1 = Class1(self.shared_signal)
        class2 = Class2(self.shared_signal)
        self.shared_signal.connect(self.action)
        class1.start()
        class2.start()
        time.sleep(1)

    def action(self, buffer):
        print(buffer)


app = QCoreApplication([])
Action = Action()
sys.exit(app.exec_())

Thank you for your help!


Solution

  • The signals themselves are thread-safe since their main task is to enqueue the information and a mutex is used for this.

    
                    ┌----------------------┐
                    |                      |
                    |         QUEUE        |
                    |                      |
                    └----------------------┘
                      ▲ ▲ ... ▲   | |     |
                      | |     |   ▼ ▼ ... ▼
                       SIGNALS      SLOTS
    
    

    What may not be thread-safe is the connection since it depends on the type of connection. I recommend you read the docs to know what kind of connections are insecure (for example using Qt::DirectConnection between QObjects that live on different threads).


    In your case the connection is safe, on the other hand I see time.sleep unnecessary but that can cause the object to be destroyed before invoking the signal so a possible solution is:

    import sys
    
    
    from PyQt5.QtCore import pyqtSignal, pyqtSlot, QCoreApplication, QObject, QThread
    
    
    class Class2(QThread):
        def __init__(self, signal, parent=None):
            super().__init__(parent)
            self.signal2 = signal
    
        def run(self):
            self.signal2.emit("Class 2 signal emitted")
    
    
    class Class1(QThread):
        def __init__(self, signal, parent=None):
            super().__init__(parent)
            self.signal1 = signal
    
        def run(self):
            self.signal1.emit("Class 1 signal emitted")
    
    
    class Action(QObject):
        shared_signal = pyqtSignal(str)
    
        def __init__(self):
            super().__init__()
            class1 = Class1(self.shared_signal, self)
            class1.finished.connect(class1.deleteLater)
    
            class2 = Class2(self.shared_signal, self)
            class2.finished.connect(class2.deleteLater)
    
            self.shared_signal.connect(self.action)
            class1.start()
            class2.start()
    
        @pyqtSlot(str)
        def action(self, buffer):
            print(buffer)
    
    
    def main():
    
        app = QCoreApplication([])
        action = Action()
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()