By default, Qt automatically queues signals when they are emitted across threads. To do this, it serializes the signal parameters and then posts an event to the event-queue of the receiving thread, where any connected slots will eventually be executed. Signals emitted in this way are therefore guaranteed to be thread-safe.
With regard to external threads, the Qt docs state the following:
Note: Qt's threading classes are implemented with native threading
APIs; e.g., Win32 and pthreads. Therefore, they can be used with
threads of the same native API.
In general, if the docs state that a Qt API is thread-safe, that guarantee applies to all threads that were created using the same native library - not just the ones that were created by Qt itself. This means it is also safe to explicitly post events to other threads using such thread-safe APIs as postEvent()
and invoke()
.
There is therefore no real difference between using threading.Thread
and QThread
when it comes to emitting cross-thread signals, so long as both Python and Qt use the same underlying native threading library. This suggests that one possible reason to prefer using QThread
in a PyQt application is portability, since there will then be no danger of mixing incompatible threading implementations. However, it is highly unlikely that this issue will ever arise in practice, given that both Python and Qt are deliberately designed to be cross-platform.
As to the question of which thread the slot
will be executed in - for both Python and Qt, it will be in the main thread. By contrast, the run
method will be executed in the worker thread. This is a very important consideration when doing multi-threading in a Qt application, because it is not safe to perform gui operations outside the main thread. Using signals allows you to safely communicate between the worker thread and the gui, because the slot connected to the signal emitted from the worker will be called in the main thread, allowing you to update the gui there if necessary.
Below is a simple script that shows which thread each method is called in:
import sys, time, threading
from PyQt5 import QtCore, QtWidgets
def thread_info(msg):
print(msg, int(QtCore.QThread.currentThreadId()),
threading.current_thread().name)
class PyThreadObject(QtCore.QObject):
sig = QtCore.pyqtSignal()
def start(self):
self._thread = threading.Thread(target=self.run)
self._thread.start()
def run(self):
time.sleep(1)
thread_info('py:run')
self.sig.emit()
class QtThreadObject(QtCore.QThread):
sig = QtCore.pyqtSignal()
def run(self):
time.sleep(1)
thread_info('qt:run')
self.sig.emit()
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.pyobj = PyThreadObject()
self.pyobj.sig.connect(self.pyslot)
self.pyobj.start()
self.qtobj = QtThreadObject()
self.qtobj.sig.connect(self.qtslot)
self.qtobj.start()
def pyslot(self):
thread_info('py:slot')
def qtslot(self):
thread_info('qt:slot')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
thread_info('main')
sys.exit(app.exec_())
Output:
main 140300376593728 MainThread
py:run 140299947104000 Thread-1
py:slot 140300376593728 MainThread
qt:run 140299871450880 Dummy-2
qt:slot 140300376593728 MainThread
.emit()
from a Python thread. – Digestion