PyQt使用多线程的正确姿势

PyQt中正确的多线程使用方式不是继承QThread,而应创建QObject对象包含业务逻辑,然后将其移动到QThread中。避免在QThread中处理业务,确保线程作为类变量,使用信号槽机制协调执行。QMutex线程锁用于同步任务和防止数据竞争。

继承QThread实现多线程,是一种错误的做法,具体见:

You’re doing it wrong…

The main thing to keep in mind when using a QThread is that it’s not a thread. It’s a wrapper around a thread object. This wrapper provides the signals, slots and methods to easily use the thread object within a Qt project. This should immediately show why the recommended way of using QThreads in the documentation, namely to sub-class it and implement your own run() function, is very wrong. A QThread should be used much like a regular thread instance: prepare an object (QObject) class with all your desired functionality in it. Then create a new QThread instance, push the QObject onto it using moveToThread(QThread*) of the QObject instance and call start() on the QThread instance. That’s all. You set up the proper signal/slot connections to make it quit properly and such, and that’s all.

意就是说:不要把QThread当成一个thread,它仅仅是用来把object包装为thread对象的包装器而已。这个包装器提供了信号,槽等各种易使用的方法。官方文档中提到的继承QThead类,然后重写run方法,是一种错误的方式。正确方式是:创建一个QObject对象,这个对象包含了你想调用的函数,然后创建一个QThread实例,把QObject对象通过moveToThread()方法放到线程里,通过调用QThread的start()方法来启动QObject的方法,从而实现线程操作。

QtCore.QThread是一个管理线程的类,当我们使用其构造函数的时候,便新建了一个线程。这里要强调,QThread是一个线程管理器,不要把业务逻辑放在这个类里面,Qt的作者已经多次批评继承QThread类来实现业务逻辑的做法。正确的姿势应该是:

self.worker_thread = QtCore.QThread()
self..moveToThread(self.worker_thread)
self.worker_thread.start()

therad定义成局部变量,会发生主进程结束而子线程仍未结束的错误

worker_thread = QtCore.QThread() 

解决方法是将thread定义为类变量

self.worker_thread = QtCore.QThread()

将业务逻辑写在一个继承了QtCore.QObject的子类里面,然后新建一个实例。
例如上述代码的woker,然后调用继承了父类的方法moveToThread方法,把该对象放进线程里面。剩下的工作就是通过信号和槽的机制处理业务逻辑和返回结果了。一般来说,槽函数所在的类在哪个线程,这个函数就在哪个线程执行。

在类成员变量里定义signal:

foo_signal = QtCore.pyqtSignal(int)
stop_signal = QtCore.pyqtSignal()
from PyQt5 import QtCore

class FooObject(QtCore.QObject):
    # signal 要在__init__方法之前定义
    foo_signal = QtCore.pyqtSignal(int)
    stop_signal = QtCore.pyqtSignal()

    def __init__(self):
        super(FooObject, self).__init__()

    @QtCore.pyqtSlot()
    def run(self):
        counter = 0
        for _ in range(10):
            for i in range(5):  
                self.foo_signal.emit(counter)  # 发射信号,参数将被槽函数接收
                counter += 1
            QtCore.QThread.sleep(1)

if __name__ == "__main__":
    import sys

    app = QtCore.QCoreApplication(sys.argv)
    foo = FooObject()
    thread = QtCore.QThread()
    thread.start()  # 开启任务线程
    foo.moveToThread(thread)
    foo.foo_signal.connect(print)  # 连接槽函数
    QtCore.QTimer.singleShot(1000, foo.run)
    
    sys.exit(app.exec_())
  • 建立信号与槽的连接
foo.foo_signal.connect(print)  # 这里连接的是Python内置的print()方法
  • 启动线程
thread.start()
  • 发射信号
self.foo_signal.emit(counter)

需要注意:仅当同步任务结束或子线程中有睡眠(sleep)时槽函数才会被调用

  • 结束线程的方法
if self.thread.isRunning():
    self._thread.quit()
    # self._thread.terminate()# 强制结束

线程锁(QMutex)

在业务逻辑的方法中加入线程锁

qmut = QMutex() # 创建线程锁

class FooObject(QObject):  # 线程1
    def __init__(self):
        super().__init__()

    def run(self):
        qmut.lock() # 加锁
        values = [1, 2, 3, 4, 5]
        for i in values:
            print(i)
            time.sleep(0.5)  # 休眠
        qmut.unlock() # 解锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值