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类即可,需要注意以下两点:

示例如下:

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中加锁不就行了吗?非也!若在此加锁,那么在那里解锁呢?每次循环都解锁一次吗?没有加锁的循环,
如果解锁的话便会报错。所以每次循环都加锁与解锁一次是最理想的。

本文链接:https://my.lmcjl.com/post/14456.html

展开阅读全文

4 评论

留下您的评论.