(三)多线程编程
如果一次只完成一件事情,那是一个不错的想法,但事实上很多事情都是同时进行的,所以在Python中为了模拟这种状态,引入了线程机制,简单地说,当程序同时完成多件事情时,就是所谓的多线程程序。多线程应用广泛,开发人员可以使用多线程程序对要执行的操作分段执行,这样可以大大提高程序的运行速度和性能。
我们先对线程的分类及概述做简单介绍,然后详细讲解Python中进行线程编程的主要两个类——QTimer计时器类、QThread线程类,并对线程的具体实现进行详细讲解。之后,我们就可以熟悉使用Python进行线程编程的基础知识,并在实际开发中应用线程处理多任务问题。
1. 线程概述
世间万物都会同时完成很多工作,例如,人体同时进行呼吸、血液循环、思考问题等活动,用户既可以使用计算机听歌,又可以使用它打印文件,而这些活动完全可以同时进行,这种思想放在Python中被称为并发,而将并发完成的每一件事情称为线程。
1. 线程的定义与分类
先了解一个概念——进程。系统中资源分配和资源调度的基本单位,叫做进程。其实进程很常见,我们使用的QQ、Word、甚至是输入法等,每个独立执行的程序在系统中都是一个进程。
每个进程中都可以同时包含多个线程,例如,QQ是一个聊天软件,但它的功能有很多,如收发信息、播放音乐、查看网页和下载文件等,这些工作可以同时运行并且互不干扰,这就是使用了线程的并发机制,我们把QQ这个软件看作一个进程,而它的每一个功能都是一个可以独立运行的线程。
上面介绍了一个进程可以包括多个线程,但计算机的CPU只有一个,那么这些线程是怎么做到并发运行的呢?Windows操作系统是多任务操作系统,它以进程为单位,每个独立执行的程序称为进程,在系统中可以分配给每个进程一段有限的使用CPU的时间(也可以称为CPU时间片),CPU在片段时间中执行某个进程,然后下一个时间片又跳至另一个进程中去执行。由于CPU转换较快,所以使得每个进程好像是同时执行一样。
一个线程则是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。
2. 多线程的优缺点
一般情况下,需要用户交互的软件都必须尽可能快地对用户的操作做出反应,以便提供良好的用户体验,但同时它又必须执行必要的计算以便尽可能快地将数据呈现给用户,这时可以使用多线程来实现。
1. 多线程的优点
要提高对用户的相应速度,使用多线程是一种最有效的方式,在具有一个处理器的计算机上,多线程可以通过利用用户事件之间很小的时间段在后台处理数据来达到这种效果。多线程的优点如下:
- 通过网络与Web服务器和数据库进行通信。
- 执行占用大量时间的操作。
- 区分具有不同优先级的任务。
- 使用户界面可以在将时间分配给后台任务时仍能快速做出响应。
2. 多线程的缺点
多线程有好处,同时也有坏处,建议一般不要在程序中使用太多的线程,这样可以最大限度地减少操作系统资源的使用,并提高性能。使用多线程可能对程序造成的负面影响如下:
- 系统将为进程和线程所需的上下文信息使用内存。因此,可以创建的进程和线程的数目会受到可用内存的限制。
- 跟踪大量的线程将占用大量的处理器时间。如果线程过多,则其中大多数线程都不会产生明显的进度。如果大多数线程处于一个进程中,则其他进程中的线程的调度频率就会很低。
- 使用多个线程控制代码执行非常复杂,并可能产生许多Bug。
- 销毁线程需要了解可能发生的问题并进行处理。
在PyQt5中实现多线程主要有两种方法,一种是使用QTimer计时器模块;另一种是使用QThread线程模块。
2. QTimer:计时器
在PyQt5程序中,如果需要周期性地执行某项操作,就可以使用QTimer类实现,QTimer类表示计时器,它可以定期发射timeout信号,时间间隔的长度在start()方法中指定,以毫秒为单位,如果要停止计时器,则需要使用stop()方法。
在使用QTimer类时,首先需要进行导入:
from PyQt5.QtCore import QTimer
示例:双色球彩票选号器
使用PyQt5实现模拟双色球选号的功能。
(1)在PyQt5的Qt Designer设计器中创建一个窗口,设置背景,并添加7个Label标签和两个PushButton按钮。
(2)将设计的窗口保存为.ui文件,并使用PyUIC工具将其转换为.py文件,同时使用qrcTOpy工具将用到的存储图片的资源文件转换为.py文件。这部分的代码如下:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(435, 294)
MainWindow.setWindowTitle("双色球彩票选号器")
MainWindow.setStyleSheet("border-image: url(./image/双色球彩票选号器.png)")
self.centralwidget=QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(97, 178,31, 31))
font = QtGui.QFont()
font.setPointSize(16)
font.setBold(True)
font.setWeight(75)
self.label.setFont(font)
self.label.setStyleSheet("color:rgb(255,255,255);")
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(128, 178, 31, 31))
self.label_2.setFont(font)
self.label_2.setStyleSheet("color:rgb(255,255,255);")
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(159, 178, 31, 31))
self.label_3.setFont(font)
self.label_3.setStyleSheet("color:rgb(255,255,255);")
self.label_3.setObjectName("label_3")
self.label_4 = QtWidgets.QLabel(self.centralwidget)
self.label_4.setGeometry(QtCore.QRect(190, 178, 31, 31))
self.label_4.setFont(font)
self.label_4.setStyleSheet("color:rgb(255,255,255);")
self.label_4.setObjectName("label_4")
self.label_5 = QtWidgets.QLabel(self.centralwidget)
self.label_5.setGeometry(QtCore.QRect(221, 178, 31, 31))
self.label_5.setFont(font)
self.label_5.setStyleSheet("color:rgb(255,255,255);")
self.label_5.setObjectName("label_5")
self.label_6 = QtWidgets.QLabel(self.centralwidget)
self.label_6.setGeometry(QtCore.QRect(252, 178, 31, 31))
self.label_6.setFont(font)
self.label_6.setStyleSheet("color:rgb(255,255,255);")
self.label_6.setObjectName("label_6")
self.label_7 = QtWidgets.QLabel(self.centralwidget)
self.label_7.setGeometry(QtCore.QRect(283, 178, 31, 31))
self.label_7.setFont(font)
self.label_7.setStyleSheet("color:rgb(255,255,255);")
self.label_7.setObjectName("label_7")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(310, 235, 51, 51))
self.pushButton.setStyleSheet("border-image: url(./image/开始.png);")
self.pushButton.setText("")
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(370, 235, 51, 51))
self.pushButton_2.setStyleSheet("border-image: url(./image/停止.png);")
self.pushButton_2.setText("")
self.pushButton_2.setObjectName("pushButton_2")
MainWindow.setCentralWidget(self.centralwidget)
self.label.setText("00")
self.label_2.setText("00")
self.label_3.setText("00")
self.label_4.setText("00")
self.label_5.setText("00")
self.label_6.setText("00")
self.label_7.setText("00")
QtCore.QMetaObject.connectSlotsByName(MainWindow)
(3)由于使用Qt Designer设计器设置窗口时,控件的背景默认会跟随窗口的背景,所以在.py文件的setupUi()方法中将7个Label标签的背景设置透明。
self.label.setAtrribute(QtCore.Qt.WA_TranslucentBackground)
self.label_2.setAtrribute(QtCore.Qt.WA_TranslucentBackground)
self.label_3.setAtrribute(QtCore.Qt.WA_TranslucentBackground)
self.label_4.setAtrribute(QtCore.Qt.WA_TranslucentBackground)
self.label_5.setAtrribute(QtCore.Qt.WA_TranslucentBackground)
self.label_6.setAtrribute(QtCore.Qt.WA_TranslucentBackground)
self.label_7.setAtrribute(QtCore.Qt.WA_TranslucentBackground)
(4)然后定义3个槽函数strat()、num()和stop(),分别用来开始计时器、随机生成双色球数字、停止计时器的功能。
def start(self):
self.timer = QTimer(MainWindow)
self.timer.start()
self.timer.timeout.connect(self.num)
def num(self):
import random
self.label.setText("{0:02d}".format(random.randint(1, 33)))
self.label_2.setText("{0:02d}".format(random.randint(1, 33)))
self.label_3.setText("{0:02d}".format(random.randint(1, 33)))
self.label_4.setText("{0:02d}".format(random.randint(1, 33)))
self.label_5.setText("{0:02d}".format(random.randint(1, 33)))
self.label_6.setText("{0:02d}".format(random.randint(1, 33)))
self.label_7.setText("{0:02d}".format(random.randint(1, 16)))
def stop(self):
self.timer.stop()
由于用到了random随机数类和QTimer类,所以需要导入相应的模块。
from PyQt5.QtCore import QTimer
import random
(5)在.py文件的setupUi()方法中为“开始”和“停止”按钮的clicked信号绑定自定义的槽函数,以便在单击按钮时执行相应的操作。
self.pushButton.clicked.connect(self.start)
self.pushButton_2.clicked.connect(self.stop)
(6)为.py文件添加__main__方法。
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
global MainWindow
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
MainWindow.show()
ui.setupUi(MainWindow)
sys.exit(app.exec_())
完整的代码如下:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QTimer
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(435, 294)
MainWindow.setWindowTitle("双色球彩票选号器")
MainWindow.setStyleSheet("border-image: url(./image/双色球彩票选号器.png)")
self.centralwidget=QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(97, 178,31, 31))
font = QtGui.QFont()
font.setPointSize(16)
font.setBold(True)
font.setWeight(75)
self.label.setFont(font)
self.label.setStyleSheet("color:rgb(255,0,0);")
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(134, 178, 31, 31))
self.label_2.setFont(font)
self.label_2.setStyleSheet("color:rgb(255,0,0);")
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(171, 178, 31, 31))
self.label_3.setFont(font)
self.label_3.setStyleSheet("color:rgb(255,0,0);")
self.label_3.setObjectName("label_3")
self.label_4 = QtWidgets.QLabel(self.centralwidget)
self.label_4.setGeometry(QtCore.QRect(205, 178, 31, 31))
self.label_4.setFont(font)
self.label_4.setStyleSheet("color:rgb(255,0,0);")
self.label_4.setObjectName("label_4")
self.label_5 = QtWidgets.QLabel(self.centralwidget)
self.label_5.setGeometry(QtCore.QRect(239, 178, 31, 31))
self.label_5.setFont(font)
self.label_5.setStyleSheet("color:rgb(255,0,0);")
self.label_5.setObjectName("label_5")
self.label_6 = QtWidgets.QLabel(self.centralwidget)
self.label_6.setGeometry(QtCore.QRect(273, 178, 31, 31))
self.label_6.setFont(font)
self.label_6.setStyleSheet("color:rgb(255,0,0);")
self.label_6.setObjectName("label_6")
self.label_7 = QtWidgets.QLabel(self.centralwidget)
self.label_7.setGeometry(QtCore.QRect(283, 178, 31, 31))
self.label_7.setGeometry(QtCore.QRect(307, 178, 31, 31))
self.label_7.setFont(font)
self.label_7.setStyleSheet("color:rgb(0,0,255);")
self.label_7.setObjectName("label_7")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(310, 235, 51, 51))
self.pushButton.setStyleSheet("border-image: url(./image/开始.png);")
self.pushButton.setText("")
self.pushButton.setObjectName("pushButton")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(370, 235, 51, 51))
self.pushButton_2.setStyleSheet("border-image: url(./image/停止.png);")
self.pushButton_2.setText("")
self.pushButton_2.setObjectName("pushButton_2")
MainWindow.setCentralWidget(self.centralwidget)
self.label.setText("00")
self.label_2.setText("00")
self.label_3.setText("00")
self.label_4.setText("00")
self.label_5.setText("00")
self.label_6.setText("00")
self.label_7.setText("00")
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.label.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.label_2.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.label_3.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.label_4.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.label_5.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.label_6.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.label_7.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.pushButton.clicked.connect(self.start)
self.pushButton_2.clicked.connect(self.stop)
def start(self):
self.timer = QTimer(MainWindow)
self.timer.start()
self.timer.timeout.connect(self.num)
def num(self):
import random
self.label.setText("{0:02d}".format(random.randint(1, 33)))
self.label_2.setText("{0:02d}".format(random.randint(1, 33)))
self.label_3.setText("{0:02d}".format(random.randint(1, 33)))
self.label_4.setText("{0:02d}".format(random.randint(1, 33)))
self.label_5.setText("{0:02d}".format(random.randint(1, 33)))
self.label_6.setText("{0:02d}".format(random.randint(1, 33)))
self.label_7.setText("{0:02d}".format(random.randint(1, 16)))
def stop(self):
self.timer.stop()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
global MainWindow
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
MainWindow.show()
ui.setupUi(MainWindow)
sys.exit(app.exec_())
运行程序,单击“开始”按钮,红球和蓝球同时滚动,单击“停止”按钮,则红球和蓝球停止滚动,当前显示的数字就是程序选中的号码。
1. QThread:线程类
PyQt5通过使用QThread类实现线程。本节介绍如何使用QThread类实现线程,并且介绍线程的生命周期。
1. 线程的实现
QThread类是PyQt5中的核心线程类,要实现一个线程,需要创建QThread类的一个子类,并且实现其run()方法。
QThread类的常用方法及说明
方法 | 说明 |
---|
run() | 线程的起点,在调用start()之后,新创建的线程将调用该方法。 |
start() | 启动线程。 |
wait() | 阻塞线程。 |
sleep() | 以秒为单位休眠线程。 |
msleep() | 以毫秒为单位休眠线程。 |
usleep() | 以微秒为单位休眠线程。 |
quit() | 退出线程的事件循环并返回代码0(成功),相当于exit(0)。 |
exit() | 退出线程的事件循环,并返回代码,如果返回0则表示成功,任何非0值都表示错误。 |
terminate() | 强制终止线程,在terminate()之后应该使用wait()方法,以确保当线程终止时,等待线程完成的所有线程都将被唤醒;另外,不建议使用这种方法终止线程。 |
setPriority() |
-
设置线程优先级,取值如下:
- QThread.IdlePriority:空闲优先级;
- QThread.LowestPriority:最低优先级;
- QThread.LowPriority:低优先级;
- QThread.NormalPriority:系统默认优先级;
- QThread.HighPriority:高优先级;
- QThread.HighestPriority:最高优先级;
- QThread.TimeCriticalPriority:尽可能频繁地分配执行;
- QThread.InheritPriority:默认值,使用与创建线程相同的优先级。
|
isFinished() | 是否完成。 |
isRunning() | 是否正在运行。 |
QThread类的常用信号及说明
信号 | 说明 |
---|
started | 在调用run()方法之前,在相关线程开始执行时从该线程发射。 |
finished | 在相关线程完成执行之前从该线程发射。 |
示例:在线程中叠加数数
在PyCharm中新建一个.py文件,使用QThread类创建线程,在重写的run()方法中以每隔1秒的频率叠加输出数字,并且在数字为10时,退出线程;最后添加主运行方法。
完整代码如下:
from PyQt5.QtCore import QThread
class Thread(QThread):
def __init__(self):
super(Thread, self).__init__()
def run(self):
num = 0
while True:
num = num + 1
print(num)
Thread.sleep(1)
if num == 10:
Thread.quit()
if __name__ == "__main__":
import sys
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
thread = Thread()
thread.start()
sys.exit(app.exec_())
运行程序,在PyCharm的控制台中每隔1秒输出一个数字,数字为10时,退出程序。
2. 线程的生命周期
任何事物都有始终,就像人的一生,就经历了少年、壮年、老年…,这就是一个人的生命周期。
线程也有自己的生命周期,包含5种状态,分别是出生状态、就绪状态、运行状态、暂停状态(包括休眠、等待和阻塞等)、死亡状态。出生状态就是线程被创建时的状态;当线程对象调用start()方法后,线程处于就绪状态(又称为可执行状态);当线程得到系统资源后就进入运行状态。
一旦线程进入运行状态,它会在就绪和运行状态下转换,同时也可能进入暂停或死亡状态。当处于运行状态的线程调用sleep()、wait()或者发生阻塞时,会进入暂停状态;当在休眠结束或者发生阻塞解除时,线程会重新进入就绪状态;当线程的run()方法执行完毕,或者线程发生错误、异常时,线程就进入死亡状态。
3. 线程的应用
使用PyQt5中的QThread类模拟龟兔赛跑的故事。
示例:龟兔赛跑
在Qt Designer设计器中创建一个窗口,在其中添加两个Label控件,分别用来标识兔子和乌龟的比赛记录;添加两个TextEdit控件,分别用来实时显示兔子和乌龟的比赛动态;添加一个PushButton控件,用来执行开始比赛操作。窗口设计完成后保存为.ui文件,并使用PyUIC工具将其转换为.py文件。
在.py文件中,分别通过继承QThread类定义兔子线程类和乌龟线程类,这两个类的实现思路一致,主要通过自定义信号发射兔子和乌龟的比赛动态,区别是,兔子在90米处,会有“兔子在睡觉”的动态。然后在主窗口中创建定义的两个线程类对象,并使用start()方法启动。
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import *
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(367, 367)
MainWindow.setWindowTitle("龟兔赛跑")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label=QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(40, 10, 91, 21))
self.label.setObjectName("label")
self.label.setText("兔子的比赛记录")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(10, 40, 161, 291))
self.textEdit.setObjectName("textEdit")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(220, 10, 91, 21))
self.label_2.setObjectName("label_2")
self.label_2.setText("乌龟的比赛记录")
self.textEdit_2 = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit_2.setGeometry(QtCore.QRect(190, 40, 161, 291))
self.textEdit_2.setObjectName("textEdit_2")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(140, 340, 75, 23))
self.pushButton.setObjectName("pushButton")
self.pushButton.setText("开始比赛")
MainWindow.setCentralWidget(self.centralwidget)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.r = Rabbit()
self.r.sinOut.connect(self.rabbit)
self.t = Tortoise()
self.t.sinOut.connect(self.tortoise)
self.pushButton.clicked.connect(self.start)
def start(self):
self.r.start()
self.t.start()
def rabbit(self, str):
self.textEdit.setPlainText(self.textEdit.toPlainText() + str)
def tortoise(self, str):
self.textEdit_2.setPlainText(self.textEdit_2.toPlainText() + str)
class Rabbit(QThread):
sinOut = pyqtSignal(str)
def __init__(self):
super(Rabbit, self).__init__()
def run(self):
for i in range(1, 11):
QThread.msleep(100)
self.sinOut.emit("\n 兔子跑了" + str(i) + "0米")
if i == 9:
self.sinOut.emit("\n 兔子在睡觉")
QThread.sleep(5)
if i == 10:
self.sinOut.emit("\n 兔子到达终点")
class Tortoise(QThread):
sinOut = pyqtSignal(str)
def __init__(self):
super(Tortoise, self).__init__()
def run(self):
for i in range(1, 11):
QThread.msleep(500)
self.sinOut.emit("\n 乌龟跑了" + str(i) + "0米")
if i == 10:
self.sinOut.emit("\n 乌龟到达终点")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
运行程序,单击“开始比赛”按钮,当兔子跑到90米处时,开始睡觉;乌龟跑至终点时,兔子醒了,随即跑至终点。
运行效果如下:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)