默认情况下,Qt automatically queues signals 当它们跨线程发出时。为此,它将信号参数序列化,然后将事件发布到接收线程的事件队列,最终将执行任何连接的插槽。因此,以这种方式发出的信号保证是线程安全的。
关于外螺纹,Qt docs state如下:
注意:Qt 的线程类是用原生线程实现的
蜜蜂;例如,Win32 和 pthreads。因此,它们可以与
同一原生 API 的线程。
一般来说,如果文档声明 Qt API is thread-safe,则该保证适用于使用相同本机库创建的所有线程 - 而不仅仅是由 Qt 本身创建的线程。这意味着使用 postEvent() 和 invoke() 等线程安全 API 将事件显式发布到其他线程也是安全的。
因此,在发出跨线程信号时,使用 threading.Thread 和 QThread 并没有真正的区别,只要 Python 和 Qt 都使用相同的底层原生线程库。这表明在 PyQt 应用程序中更喜欢使用QThread 的一个可能原因是可移植性,因为这样就不会有混合不兼容的线程实现的危险。但是,鉴于 Python 和 Qt 都被刻意设计为跨平台,因此在实践中不太可能出现此问题。
至于slot 将在哪个线程中执行的问题——对于Python 和Qt,它将在main 线程中。相比之下,run 方法将在 worker 线程中执行。在 Qt 应用程序中执行多线程时,这是一个非常重要的考虑因素,因为在主线程之外执行 gui 操作是不安全的。使用信号可以让您在工作线程和 gui 之间安全地进行通信,因为连接到工作线程发出的信号的插槽将在主线程中被调用,从而允许您在必要时更新那里的 gui。
下面是一个简单的脚本,显示了每个方法在哪个线程中被调用:
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_())
输出:
main 140300376593728 MainThread
py:run 140299947104000 Thread-1
py:slot 140300376593728 MainThread
qt:run 140299871450880 Dummy-2
qt:slot 140300376593728 MainThread