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

news/2024/11/25 17:38:45/

前言

最近学习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 timefrom 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, QApplicationclass Thread(QThread):valueChange = pyqtSignal(int)def __init__(self, *args, **kwargs):super(Thread, self).__init__(*args, **kwargs)self._isPause = False  # 是否暂停self._value = 0self.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)# 进度条不能超过100if self._value > 100:self._value = 0# 每隔0.1秒,进度条+1self._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 cgitbQApplication.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 timefrom 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中加锁不就行了吗?非也!若在此加锁,那么在那里解锁呢?每次循环都解锁一次吗?没有加锁的循环,
如果解锁的话便会报错。所以每次循环都加锁与解锁一次是最理想的。

http://www.ppmy.cn/news/507298.html

相关文章

联想e480一键恢复小孔_thinkpade480win10如何一键还原

thinkpade480win10如何一键还原 卡饭网 本站整理 2019-08-14 方法一: 在控制面板中打开“恢复”(大图标查看方式下)。 点击【开始系统还原】 选择还原点 (1)系统还原会推荐一个最近的没有故障的还原点,建议选择。点击【下一步】,再点击【完成…

E480安装ubuntu18.04出现进入wifi没有无线适配器的处理方案

今天突发奇想,想在自己的电脑上装上ubuntu,实现win10ubuntu双系统 在顺利的装好系统之后,发现wifi界面找不到适配器,也即是无线网卡没有装好 E480是rtl8821ce无线网卡,官方不提供linux驱动,github上大佬写…

Java 基础进阶篇(十八):正则表达式匹配规则和应用

文章目录 一、正则表达式概述二、正则表达式的匹配规则三、正则表达式在方法中的应用3.1 校验手机号、邮箱和座机电话号码3.2 字符串的内容替换和分割 四、编程题目4.1 表示数值的字符串4.2 非严格递增连续数字序列 一、正则表达式概述 正则表达式是对字符串(包括普…

ubuntu16.04误删高于当前版本内核,开机黑屏,只显示/dev/sda9:clean...files,...blocks,更换内核后卡在开机界面,解决安装包依赖,重新安装ubuntu桌面解决问题

1 背景介绍 1 起因 我使用的Ubuntu16.04在安装的过程中/boot只分了200M空间,导致后续更新的过程中被新内核占满了空间,当时boot中所包含的内核有*linux-image-4.15.0-28-generic,linux-image-4.15.0-43-generic,linux-image-4.1…

基于循环特征位移聚合器的车道线检测(RESA: Recurrent Feature-Shift Aggregator for Lane Detection)

2021年的车道线检测新方法。 官方公开视频、论文、源码: https://www.bilibili.com/video/BV1664y1o7wg https://arxiv.org/abs/2008.13719 https://github.com/ZJULearning/resa (该视频对现有车道线检测方法进行了分类、归纳总结,很完善。…

学习日记1

目录 Goolge Colab学习 colab简单教程 stackoverflow 卷积神经网络 深度学习迭代次数 python (),[], {} pd.DataFrame()函数解析(最清晰的解释) 欧式距离 马氏距离 深度学习 模型训练超参数调整 rnn学习 损…

37、测试Yolox+TensorRT Yolox+NCNN Yolox+Tengine

觉基本思想:最近yolox刚被放出来,因为之前很多项目都是基于TensorRT部署nano,突然想使用Tengine部署一下nano,随手记录一下 分了四步走:1)先测试一下Yolovx在PC端的性能,源码来自官方的demo和网上相关资料 …

Win10+Torch1.9+CUDA11.1成功配置YOLOX预测环境

因为windows使用较多,所以想在上面装一个pytorch环境进行学习,之前我根据个人笔记本电脑显卡型号已成功安装了显卡驱动和CUDA,安装最新版pytorch(1.9)也可以调用GPU,后面直接拿YOLOX跑了一下,发…