Pyqt5的QThead线程对象实现线程开始、暂停、恢复、结束

前言

最近学习Pyqt5,研究QThead线程对象,因网上这方面资料较少,钻研过后,将感悟理解记录如下。

声明:感悟理解建立在分析其他大佬的博客的基础上,喝水不忘挖井人,大佬们的博客如下:

https://huaweicloud.csdn.net/638071cedacf622b8df8844e.html

https://blog.csdn.net/tcy23456/article/details/107904530

https://blog.csdn.net/jeekmary/article/details/88739092

https://www.cnblogs.com/mosewumo/p/12486228.html

1、QThead线程基本使用

线程使用十分简单,我们只需要编写一个类,继承QThead类即可,需要注意以下两点:

1、在自定义线程类的初始化__init__方法中,必须调用父类的__init__方法,否则会报错。
2、重写父类的run()方法,在此执行业务逻辑。
3、声明自定义线程类的对象之后,调用对象的start()方法,就会开启线程,进入线程的run()方法,执行完run()方法之后,线程结束。

示例如下:

import sys
import time

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class MyThread(QThread):
    def __init__(self):
        super(MyThread, self).__init__()

    def run(self):
        for i in range(10):
            print(i)
            time.sleep(1)

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("PyQt5多线程学习")
        self.resize(400, 300)

        # 开启线程按钮
        btn_start = QPushButton(self)
        btn_start.setText("开始")
        btn_start.clicked.connect(self.start)


    # 开启线程
    def start(self):
        self.my_thread = MyThread()
        self.my_thread.start()

if __name__ == '__main__':
    app = QApplication(sys.argv)

    w = MyWindow()
    w.show()

    sys.exit(app.exec_())

自定义了线程类MyThread(),声明对象my_thread,调用start()方法,开启线程,会去执行线程中的run()方法中的内容,每隔一秒输出一个数字。

2、线程暂停与恢复

介绍一下如何实现QThead线程类的恢复与暂停。

我们通过QWaitCondition()QMutex()这两个类来实现这两个功能。

1、QMutex()的作用是给线程上锁与解锁,在线程暂停前先给线程上锁,防止数据状态发生改变,线程被唤醒之后则解锁。线程上锁方法为:QMutex().lock(),线程解锁方法为:QMutex().unlock()
我们只需在线程类中创建QMutex()对象,调用相应的方法即可对线程实现上锁与解锁。

2、QWaitCondition()的作用是暂停线程与恢复线程,暂停线程的方法为:QWaitCondition().wait(QMutex()),我们需要把上了锁的QMutex()对象当成参数传进去。线程恢复的方法为:QWaitCondition().wakeAll,可以恢复被wait()过的线程。QWaitCondition()用于多线程同步,一个线程自己调用QWaitCondition.wait()阻塞等待,直到另外一个线程调用QWaitCondition.wake()唤醒该线程,该线程才继续往下执行。

我们给出一个进度条的例子来熟悉这几个函数:

from PyQt5.QtCore import QThread, QWaitCondition, QMutex, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QProgressBar, QApplication


class Thread(QThread):
    valueChange = pyqtSignal(int)

    def __init__(self, *args, **kwargs):
        super(Thread, self).__init__(*args, **kwargs)
        self._isPause = False  # 是否暂停
        self._value = 0
        self.cond = QWaitCondition()
        self.mutex = QMutex()

    # 暂停(挂起)
    def pause(self):
        # 状态改为暂停
        self._isPause = True

    # 恢复
    def resume(self):
        # 状态改为恢复
        self._isPause = False
        # 调用QWaitCondition.wake()唤醒暂停的线程
        self.cond.wakeAll()

    def run(self):
        # 开启线程,一直循环重复下去,监听状态
        while 1:
            # 给线程上锁,防止线程挂起的时候,线程中的数据状态发生改变
            self.mutex.lock()

            # 如果是暂停状态,则阻塞等待
            if self._isPause:
                self.cond.wait(self.mutex)

            # 进度条不能超过100
            if self._value > 100:
                self._value = 0

            # 每隔0.1秒,进度条+1
            self._value += 1

            # 发送发信号,改变进度条的数据
            self.valueChange.emit(self._value)

            self.msleep(100)

            # 线程恢复,解锁
            self.mutex.unlock()


class Window(QWidget):

    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        layout = QVBoxLayout(self)
        self.progressBar = QProgressBar(self)
        layout.addWidget(self.progressBar)
        layout.addWidget(QPushButton('休眠', self, clicked=self.doWait))
        layout.addWidget(QPushButton('唤醒', self, clicked=self.doWake))

        self.t = Thread(self)
        # 信号可以连接自定义的函数,也可以连接默认的函数
        self.t.valueChange.connect(self.progressBar.setValue)
        self.t.start()

    # 暂停
    def doWait(self):
        self.t.pause()

    # 恢复
    def doWake(self):
        self.t.resume()


if __name__ == '__main__':
    import sys
    # import cgitb

    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    # cgitb.enable(format='text')

    app = QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())

程序中我已经添加注释,不难看懂。

程序运行截图如下:
在这里插入图片描述

在此我提几点需要注意的地方:

1、为何run方法中要用while 1来进行无限循环?

2、为何要添加一个_isPause来标识线程是否暂停?

3、为何每次循环都要加锁与解锁各一次?

能够回答如上三个问题,说明君以析其中之妙!!!善哉善哉!!!

3、线程退出

与QT类似,我相信如果上面的看懂了,看下面这篇博客也是轻松:
https://blog.csdn.net/qq_44365088/article/details/119087454

怕有些同志静不下心来看不进去,我就顺便在这里说明一下最简单的一种:

1、直接让线程对象调用terminate()方法来结束线程。终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。请在terminate()之后使用QThread().wait()

警告:此函数是危险的,不鼓励使用。线程可以在其代码路径中的任何点终止。线程可以在修改数据时终止。线程没有机会自己清理,解锁任何持有的互斥锁等。简而言之,只有在绝对必要时才使用这个函数。案例如下,接上文创建线程的代码,只是加一个结束线程的按钮:

import sys
import time

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class MyThread(QThread):
    def __init__(self):
        super(MyThread, self).__init__()

    def run(self):
        for i in range(10):
            print(i)
            time.sleep(1)

class MyWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle("PyQt5多线程学习")
        self.resize(400, 300)

        # 开启线程按钮
        btn_start = QPushButton(self)
        btn_start.setText("开始")
        btn_start.clicked.connect(self.start)

        # 结束线程按钮
        btn_end = QPushButton(self)
        btn_end.setText("结束")
        btn_end.move(100, 0)
        btn_end.clicked.connect(self.end)

    # 结束线程
    def end(self):
        self.my_thread.terminate()
        # wait函数是个阻塞的接口,意思是线程必须真的退出了,才会执行wait之后的语句,否则将会一直阻塞在这里,如果在界面上使用,需要保证线程中代码的合理性。
        self.my_thread.wait()
        print('结束线程')

    # 开启线程
    def start(self):
        self.my_thread = MyThread()
        self.my_thread.start()

if __name__ == '__main__':
    app = QApplication(sys.argv)

    w = MyWindow()
    w.show()

    sys.exit(app.exec_())

总结

在此再次感谢各位大佬的博客!!

彩蛋

我在此对如上三个问题进行解答:

1、为何run方法中要用while 1来进行无限循环?

因为进度条是每隔0.1秒就会改变一次,每次循环都会+1,这是其一。
通过while 1 来实现监听_isPause的状态,这是其二。
2、为何要添加一个_isPause来标识线程是否暂停?

线程只能在自己内部调用wait方法将自己暂停,所以需要通过一个标识来判断此时是否应该将自己暂停,并且不断对标识进行监听。
若暂停与唤醒都能在其他线程实现,则不需要标识。
3、为何每次循环都要加锁与解锁各一次?

因为无法确定此次循环,是否需要暂停,所以每次循环都加锁与解锁各一次,更加安全。
你可能会说,在:
            if self._isPause:
                self.cond.wait(self.mutex)
中不就是需要暂停吗,在这个if中加锁不就行了吗?非也!若在此加锁,那么在那里解锁呢?每次循环都解锁一次吗?没有加锁的循环,
如果解锁的话便会报错。所以每次循环都加锁与解锁一次是最理想的。
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱吃雪糕的小布丁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值