PySide6学习专栏(四):用多线程完成复杂计算任务
如果计程序中要处理一个非常庞大的数据集中的数据,且数据处理计算很复杂,造成数据处理占用大量时间和CPU资源,如果不用多线程,仅在主进程中来处理数据,将会使整个程序卡死,必须采用多线程来处理这些数据是唯一的选择,下面分别给出几种多线程的使用方法供参考:
1、示例1:一采用基于PySide6的多线程(比PYTHON自带的多线程类要好用的多)来处理大数据的示例:
程序运行界面如下:
# -*- coding: utf-8 -*-
#模块功能:演示多线程类calcuteThread及其子类trdCalcute_01、trdCalcute_02....等来执行多线程数据计算,
# 用于将大量复杂的数据计算包装到多线程类中来完成
# ID=1 ID=2两个线程演示多线程同主窗口数据通信,ID=3 ID=4演示用继承多线程基类calcuteThread
# 的子类trdCalcute_01、trdCalcute_02。。。来将大量复杂的数据分块后分别交由不同线程完成计算任务
# 一个线程类可初始化多个对象,对相同计算功能的可用一个同名线程类定义多个实例化线程对象,
# 每个线程对象执行相同的计算功能完成对拆分的大数据分块计算,不同计算功能的可分别继承定义不同功能的线程子类并实例化对象import sys,os,time,math,copy,random
from math import *
import PySide6
from PySide6 import *
from PySide6.QtCore import *
#from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QLabel,QLayout, QMainWindow, QMenu, QMenuBar,QPushButton, QSizePolicy, QStatusBar, QToolBar,QVBoxLayout, QWidget)
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,QMetaObject, QObject, QPoint, QRect,QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,QCursor, QFont, QFontDatabase, QGradient,QMovie,QIcon, QImage, QKeySequence, QLinearGradient,QPainter, QPalette, QPixmap, QRadialGradient,QTransform)import math #此方法导入库,引用库中函数时,需加上模块前缀math.
from math import * #此方法导入库,引入库中函数时,无需加上模块前缀math
import numpy as np #此方法导入库,因采用了MAX_THREAD_NUM=100 #最大允许可同时开的多线程数量#基于PySide6的演示窗口类
class MainWindow(QWidget):def __init__(self, parent=None):super(MainWindow, self).__init__(parent)self.setWindowTitle('多线程(同时开4个)示例')self.resize(900,500)#仅示例,实际数据集可能几百万个,可设置干个线程,每个线程分别处理计算数据集中的某一段数据即可,最后将处理后的计算结果全并即完成任务self.lstDatas=[0,1,2,3,4,5,6,7,8,9] #交由多线程(ID=3线程)计算处理的数据集合(可为字典,列表,np结构等,一般采用内存共享处理方式,多线程计算完成后,不必在主进程再对数据处理了)self.npDatas=np.array([0,1,2,3,4,5,6,7,8,9]) #交由多线程(ID=4线程)计算处理的np数据集合self.startTime3=self.endTime3=0.0 self.endTime4=self.endTime4=0.0 layout = QFormLayout() self.pushButton_1 = QPushButton()self.pushButton_1.setText("开始新线程1(calcuteThread基类)")self.pushButton_2 = QPushButton()self.pushButton_2.setText("开始新线程2(calcuteThread基类)")self.pushButton_3 = QPushButton()self.pushButton_3.setText("开始线程3(calcuteThread的trdCalcute_01子类")self.pushButton_4 = QPushButton()self.pushButton_4.setText("停止线程1")self.pushButton_5 = QPushButton()self.pushButton_5.setText("停止线程2")self.pushButton_6 = QPushButton()self.pushButton_6.setText("停止线程3")self.pushButton_7 = QPushButton()self.pushButton_7.setText("开始线程4(calcuteThread的trdCalcute_01子类)")self.pushButton_8 = QPushButton()self.pushButton_8.setText("停止线程4")self.edita1 = QLineEdit()self.edita1.setText('信号槽:线程1的值')self.progressBar_1=QProgressBar()self.progressBar_1.setMinimum(0)self.progressBar_1.setMaximum(100)self.edita2 = QLineEdit()self.edita2.setText('信号槽:线程2的值')self.progressBar_2=QProgressBar()self.progressBar_2.setMinimum(0)self.progressBar_2.setMaximum(100)self.edita3 = QLineEdit()self.edita3.setText('信号槽:线程3的值')self.edita4 = QLineEdit()self.edita4.setText('信号槽:线程4的值')layout.addRow("单击按纽开始线程1(线程sleep 10毫秒)->",self.pushButton_1)layout.addRow("单击按纽结束线程1->",self.pushButton_4)layout.addRow("信号槽方式返回线程1的值->",self.edita1)layout.addRow("线程1返回值(进度条展示)->",self.progressBar_1)layout.addRow("单击按纽开始线程2(线程sleep 50毫秒)->",self.pushButton_2)layout.addRow("单击按纽结束线程2->",self.pushButton_5)layout.addRow("信号槽方式返回线程2的值->",self.edita2)layout.addRow("线程2返回值(进度条展示)->",self.progressBar_2)layout.addRow("单击按纽开始线程3(计算处理list数据集中的0-4段)->",self.pushButton_3)layout.addRow("单击按纽结束线程3->",self.pushButton_6)layout.addRow("信号槽方式返回线程3对list数据集(0-4段)计算处理结果->",self.edita3)layout.addRow("单击按纽开始线程4(计算处理np数据集中的5-9段)->",self.pushButton_7)layout.addRow("单击按纽结束线程4->",self.pushButton_8)layout.addRow("信号槽方式返回线程4对np数据集(5-9段)计算处理结果->",self.edita4) self.setLayout(layout)self.pushButton_4.setEnabled(False)self.pushButton_5.setEnabled(False)self.pushButton_6.setEnabled(False)self.pushButton_8.setEnabled(False)#定义线程数组字典(相当于字典的key=线程ID号,字典的value=线程类的实例化对象)self.dicThread={} #增加的线程数量不大于AX_THREAD_NUM,每增加一个线程,字典会相应增加线程变量self.bthreadopen=np.full((MAX_THREAD_NUM),False) #定义线程打开的状况self.pushButton_1.clicked.connect(self.start_worker_1)self.pushButton_2.clicked.connect(self.start_worker_2)self.pushButton_3.clicked.connect(self.start_worker_3)self.pushButton_4.clicked.connect(self.stop_worker_1)self.pushButton_5.clicked.connect(self.stop_worker_2)self.pushButton_6.clicked.connect(self.stop_worker_3)self.pushButton_7.clicked.connect(self.start_worker_4)self.pushButton_8.clicked.connect(self.stop_worker_4)#点击按纽1的信号槽:开始线程1def start_worker_1(self):self.dicThread[1] = calcuteThread(1,None,0.01)self.bthreadopen[1]=Trueself.dicThread[1].start()self.dicThread[1].signal_ID.connect(self.threadToWindow) #将线程1中的自定义信号signal_ID绑定槽函数self.dicThreadToWindowself.dicThread[1].signal_OK.connect(self.threadOK) #将线程1中的自定义信号signal_OK绑定槽函数self.dicThreadOKself.pushButton_1.setEnabled(False)self.pushButton_4.setEnabled(True)#点击按纽2的信号槽:开始线程2def start_worker_2(self):self.dicThread[2] = calcuteThread(2,None,0.01)self.bthreadopen[2]=Trueself.dicThread[2].start()self.dicThread[2].signal_ID.connect(self.threadToWindow) #将线程2中的自定义信号signal_ID绑定槽函数self.dicThreadToWindowself.pushButton_2.setEnabled(False)self.pushButton_5.setEnabled(True)#点击按纽3的信号槽:开始线程3,用于计算数据集self.lstDatas中的0-5段数据集,比如对一个几百上千万的大数据集,分解成10块数据,用10个线程分别计算分配的子块数据集,最后将各线程计算的数据集合并计算结果即可,避免在一个主进程中来计算全部数据def start_worker_3(self):self.startTime3=time.time() #记录线程3从开始到计算完成用的时间self.edita3.setText('开始计算数据集self.lstDatas中的0-4段数据,请等待计算结果。。。。。。')lstDatas_3=[self.lstDatas,0,5] #本线程处理计算lstDatas数据中的0-4号数据,因datas为列表,在多线程中对此self.lstDatas处理后的值已是处理后值了,要避免对同一区域数据不同的地方都在计算处理,引起错误self.dicThread[3] = trdCalcute_01(3,lstDatas_3)self.bthreadopen[3]=Trueself.dicThread[3].start()self.dicThread[3].signal_OK.connect(self.threadOK) #将线程4中的自定义信号signal_OK绑定槽函数self.dicThreadOKself.pushButton_3.setEnabled(False)#点击按纽4的信号槽:开始线程4,用于计算数据集self.npDatas中的5-9段数据集def start_worker_4(self):self.startTime4=time.time() #记录线程4从开始到计算完成用的时间self.edita4.setText('开始计算数据集self.npDatas中的5-9段数据,请等待计算结果。。。。。。')lstDatas_4=[self.npDatas,5,10] #本线程处理计算npDatas数据中的5-9号数据,因npDatas为np数组,在多线程中对此self.npDatas处理后的值已是处理后值了,要避免对同一区域数据不同的地方都在计算处理,引起错误self.dicThread[4] = trdCalcute_01(4,lstDatas_4)self.bthreadopen[4]=Trueself.dicThread[4].start()self.dicThread[4].signal_OK.connect(self.threadOK) #将线程4中的自定义信号signal_OK绑定槽函数self.dicThreadOKself.pushButton_7.setEnabled(False)#点击按纽4的信号槽:结束线程1def stop_worker_1(self):self.dicThread[1].bLooping=Falsetime.sleep(0.5)self.dicThread[1].stop()self.pushButton_1.setEnabled(True)self.pushButton_4.setEnabled(False)self.bthreadopen[1]=False#点击按纽5的信号槽:结束线程2def stop_worker_2(self):self.dicThread[2].stop()self.pushButton_2.setEnabled(True)self.pushButton_5.setEnabled(False)self.bthreadopen[2]=False#点击按纽6的信号槽:结束线程3def stop_worker_3(self):self.dicThread[3].stop()self.pushButton_3.setEnabled(True)self.pushButton_6.setEnabled(False)self.bthreadopen[3]=False#点击按纽6的信号槽:结束线程4def stop_worker_4(self):self.dicThread[4].stop()self.pushButton_7.setEnabled(True)self.pushButton_8.setEnabled(False)self.bthreadopen[4]=False#绑定线程类中的signal_ID信号对应的槽函数,及时得到各线程中的变量一循环累加变量的值,用进度条形式体现出来#所有线程发送给主窗口的信号均绑定此槽函数,用ID号区分是哪个线程发的信号及值def threadToWindow(self,counter):ID = self.sender().ID #在槽函数中被调用,用于获取发出信号的对象的索引号,等同于self.dicThread[n].IDss=f'线程{ID}在多线程通过信号槽发送到主窗口的值={counter}'if ID == 1:self.progressBar_1.setValue(counter)self.edita1.setText(ss)elif ID == 2:self.progressBar_2.setValue(counter)self.edita2.setText(ss)#。。。。。。#所有线程发送signal_OK信号给主窗口均绑定此槽函数:判断用于分担计算的线程是否完成分配的全部计算工作def threadOK(self,perWork): ID = self.sender().ID #在槽函数中被调用,用于获取发出信号的对象的索引号,等同于self.dicThread[n].IDprint(f'多线程{ID}计算完成百分比={perWork}%')perValue=int(perWork) #因线程中定义的发信号函数参数为object,因实际发出的值类型是int,需要在此转换为int,如是字符串,转换用str(perWork)等,如是列表转换用list()等if ID == 3:if(perValue==100):self.endTime3=time.time() #记录线程3计算完成时间self.edita3.setText(f'多线程{ID}中将数据0-5项值*10后={self.lstDatas}')reValue=self.dicThread[ID].getCalcuteValue()print(f'\n线程{ID}中变量计算值self.test={reValue}')print(f'线程3计算共用时={self.endTime3-self.startTime3}秒\n')self.pushButton_6.setEnabled(True)if ID == 4:if(perValue==100):self.endTime4=time.time() #记录线程4计算完成时间self.edita4.setText(f'多线程{ID}中将数据5-9项值*20后={self.npDatas}') reValue=self.dicThread[ID].getCalcuteValue()print(f'\n线程{ID}中变量计算值self.test={reValue}')print(f'线程4计算共用时={self.endTime4-self.startTime4}秒\n')self.pushButton_8.setEnabled(True)#。。。。。。 #************************************************************************************************************************
#自定义计算用多线程类基类(继承PySide6的多线程类QtCore.QThread,不是PYTHON的线程类)
class calcuteThread(QtCore.QThread):signal_ID = Signal(int) #示例:自定义线程中的信号,名称为signal_ID,用于向主窗体发送本线程的ID号,参数int,表示发出的信号为整数,参数类型可为int float str list object等signal_OK = Signal(object) #向主窗体发送当前线程完成计算量的百分比,当为100时,表示线程计算任务完成,参数object表示发出的变量可作任意对象类型,如列表,字典,控件,窗体等等# 构造参数: 线程ID 计算用数据集合 线程中休眠时间 线程对应窗口 lstDatas格式=[数据集1,数据集2,......] 在调用处将数据集打包到此列表中供多线程使用,可多层嵌套列表、数组等def __init__(self,ID, lstDatas=None,sleepTime=0.01,parent=None):super(calcuteThread,self).__init__(parent)self.ID = ID #线程的ID号,在定义线程处的对象可以用self.sender().ID得到此值self.lstDatas=lstDatas #用于多线程计算处理的全部数据集合(列表方式为内存共享,处理时需要注意主界面如也同时处理这些数据或其他线程也同时处理这些数据时的内容一致性问题)self.sleepTime = sleepTime #线程计算过程中如使用sleep休眠的时间间隔self.bLooping= False #此变量用于在run函数中控制可能用到的无限循环体,为False时,退出此无限循环体self.workOK=False #本线程的数据处理工作是否全部完成self.workPersent=0 #本线程的数据处理工作完成的比例(0-100) self.testN=0 #测试用一自加的整数 #重载run函数:当在调用端调用线程的start()方法后,线程默认第一个被自动启动的函数即为run(),run执行完,并不表示线程已退出,要退出用线程对象.stop()def run(self):print(f'calcuteThread基类开始线程run函数,线程ID={self.ID}')self.bLooping= True #用于控制无限循环体的开关,用无限循环处理一适合在循环体内处理的事务时,外部通过更改此开关变量为False时退出此无限循环(无限循环体中应使用time.sleep(秒)间隔代码,否则会造成主界面操作卡顿)while(self.bLooping): self.testN+=1 #示例,测试用一自加1的变量 if self.testN==99: self.testN=0if(self.ID==1):pass #对不同的线程ID如果有不同的处理内容elif(self.ID==2):pass #...... time.sleep(self.sleepTime) #休眠间隔,防系统卡self.signal_ID.emit(self.testN) #将本线程中的testN值(0-99)通过信号signal_ID发送出去,窗口接收端通过绑定信号signal_ID的槽函数可及时接收于此数据#停止退出线程def stop(self):print(f'停止线程{self.ID}')self.terminate()#得到本线程的IDdef getID(self):return self.ID#自定义继承calcuteThread多线程子类01,此子类对应完成某一种类型的计算功能:
class trdCalcute_01(calcuteThread):def __init__(self,ID, lstDatas=None,sleepTime=0.01,parent=None):super().__init__(ID,lstDatas,sleepTime,parent) #执行多线程基类构造self.info=f'本线程ID号={ID},用于计算第一种功能' self.test=0.0#重载run函数:如不显示调用其父类的run,则父类的run()方法将不会被调用def run(self):print(f'trdCalcute_01多线程子类开始run{self.ID}')#super().run() #调用父类calcuteThread的run方法,这里不用self.test_calcute1() #执行计算1self.workPersent=100 #多线程计算作任务完成的百分比值self.signal_OK.emit(self.workPersent) #将本线程中的完成计算任务的百分比发送回调用处,当达到100时,调用处进则关闭此线程和进行下一步#多线程数据计算函数1示例def test_calcute1(self):if(self.lstDatas==None):returnif(len(self.lstDatas)==0):return n=10loopNum=3000000if(self.ID==3):n=10 #测试:3线程对list列表的0-5元素*10elif(self.ID==4):n=20 #测试:4线程对np数组的5-9元素*20loopNum=5000000lst1=self.lstDatas[0] #多线程中要处理的数据集合示例,ID=3线程对应的是list列表,ID=4线程为np一维数组结构print(lst1)startPos=self.lstDatas[1] #本多线程实例要处理的数据区间起点号endPos=self.lstDatas[2] #本多线程实例要处理的数据区间终点号for index in range(startPos,endPos):lst1[index]=lst1[index]*n #直接对列表成员操作,因列表为内存共享,调用处的列表(或字典,np等)变量在多线程中就处理完成,不必在主进程中再处理此列表变量了,注意避免不同线程或进程对同一区域的数据同时处理出现不可预知的错误(要同时也行,用线程锁,将会降低效率)for i in range(loopNum): #模拟复杂计算:循环300万和500万次的一个计算测试self.test+=sqrt(sqrt(sqrt(sqrt(i**1.23456789**0.975312468)*sin(i**0.123456789/pi)*sqrt(i**0.0123456789**0.13579**0.2468/pi))))self.workOK=True #本线程要计算的任务已全部完成#如必要,从线程返回计算结果.如传入线程的数据集是列表,字典等数据,因是内存共享,在线程计算完成后,共享数据部分在调用处已无需再处理,非内存共享数据从此函数中返回得到 def getCalcuteValue(self):return self.test #自定义继承calcuteThread多线程子类02,此子类对应完成xxxx种类型的计算功能:
class trdCalcute_02(calcuteThread):def __init__(self,ID, lstDatas=None,sleepTime=0.01,parent=None):super().__init__(ID,lstDatas,sleepTime,parent) #执行多线程基类构造self.info=f'本线程ID号={ID},用于计算第二种计算功能' #重载run函数:如不显示调用其父类的run,则父类的run()方法将不会被调用def run(self):print(f'trdCalcute_02多线程子类开始run{self.ID}')#super().run() #调用父类calcuteThread的run方法,这里不用self.test_calcute2() #执行计算self.workPersent=100 #多线程计算作任务完成的百分比值self.signal_OK.emit(self.workPersent) #将本线程中的完成计算任务的百分比发送回调用处,当达到100时,调用处进则关闭此线程和进行下一步#多线程数据计算函数2示例def test_calcute2(self):if(self.lstDatas==None):returnif(len(self.lstDatas)==0):return #计算测试for i in range(50000000): #循环50000000的一个计算测试self.test+=sqrt(sqrt(sqrt(sqrt(i**1.23456789**0.975312468)*sin(i**0.123456789/pi)*sqrt(i**0.0123456789**0.13579**0.2468/pi))))self.workOK=True #本线程要计算的任务已全部完成#如必要,从线程返回计算结果.如传入线程的数据集是列表,字典等数据,因是内存共享,在线程计算完成后,共享数据部分在调用处已无需再处理,非内存共享数据从此函数中返回得到 def getCalcuteValue(self):return self.test#自定义异常类,在使用代码处如认为出现异常,用代码raise customExcept('自定义异常,ID=1',1) ,异常处thy:.... except customExcept as e: print(e.msg)
class customExcept(Exception):def __init__(self,msg,ID=0):self.msg=msgif(ID==1):self.msg='自定义异常1'elif(ID==2):self.msg='自定义异常2'#########################################################################################
if __name__=="__main__": app = QApplication([])mainWin = MainWindow()mainWin.show()app.exec()
2、示例2,在采用多线程时,如果对同一数据有多个线程在某个时间点同时要处理,就可能存在数据处理的结果不是预想中的了(上例是分段各处理各的,不存在此问题),就需要考虑用线程锁,即对某一数据处理前先加上锁表示处理过程中不准其他线程再使用此数据,处理完成后解锁此数据,其他线程就可以使用此数据了。
"""
1示例:同时开启的多线程因线程处理时序不同,对共有全局变量的引用值可能是同预期值是不同的
2示例:使用线程锁,来避免1示例可能出现的情况,但会降低线程使用效率
"""
import threading
import time
from timeit import default_timer as timer
import time
from time import *import sys
statrtime=0.00 #线程起止时间
endtime=0.00
Lock = threading.Lock()
# myThread继承父类(无锁示例),并进行重写
class myThread1(threading.Thread):# 重写父类的构造函数count=0lst=['类全局成员列表值=',0,'列表中的循环计数=',0]def __init__(self, number, index):threading.Thread.__init__(self)self.number = number # 添加number变量self.id = index # 添加letter变量# 重写父类中的run函数def run(self):if(self.id==1):print(f"【线程开始】{self.name}")self.task1(self.number, self.id)elif(self.id==2):print(f"【线程开始】{self.name}")self.task2(self.number, self.id)# print("【线程结束】", self.name)# 重写父类析构函数def __del__(self):print("【线程销毁释放内存】", self.name)# 自定义的函数:供线程1调用def task1(self, number, id):count=1i=1while i <= number: #sleep(0.1)myThread1.lst[1]=i myThread1.lst[3]=count myThread1.count+=1num=myThread1.counts=''sleep(0.04) #因休眠期,可能存在其他跑得快的线程将此全局变量改了,即myThread.count不一定是连续的if(myThread1.count!=num):s='两值已不相同'print(f'\ntask1:线程{id}中lst={myThread1.lst},线程类全局计数count={myThread1.count},对比sleep前值={num}:{s}')i+=1 i+=1count+=1# 自定义的函数,供线程2调用def task2(self, number, id):count=1i=1m = 1while i <= number: myThread1.lst[1]=i myThread1.lst[3]=count myThread1.count+=1num=myThread1.counts=''sleep(0.01) #因休眠期,可能存在其他跑得快的线程将此全局变量改了,即myThread.count不一定是连续的if(myThread1.count!=num):s='两值已不相同'print(f'\ntask2:线程{id}中lst={myThread1.lst},线程类全局计数count={myThread1.count},对比sleep前值={num}:{s}')i+=1count+=1# myThread2继承父类(有锁示例),并进行重写
class myThread2(threading.Thread):# 重写父类的构造函数count=0lst=['类全局成员列表值=',0,'列表中的循环计数=',0]def __init__(self, number, index):threading.Thread.__init__(self)self.number = number # 添加number变量self.id = index # 添加letter变量# 重写父类中的run函数def run(self):if(self.id==1):print(f"【线程开始】{self.name}")self.task3(self.number, self.id)elif(self.id==2):print(f"【线程开始】{self.name}")self.task4(self.number, self.id)# print("【线程结束】", self.name)# 重写父类析构函数def __del__(self):print("【线程销毁释放内存】", self.name)# 自定义的函数:供线程2调用def task3(self, number, id):count=1i=1while i <= number: #sleep(0.1)Lock.acquire() # 设置线程锁myThread2.lst[1]=i myThread2.lst[3]=count myThread2.count+=1num=myThread2.counts=''sleep(0.04) #因休眠期,可能存在其他跑得快的线程将此全局变量改了,即myThread.count不一定是连续的if(myThread2.count!=num):s='两值已不相同'print(f'\ntask3:线程{id}中lst={myThread2.lst},线程类全局计数count={myThread2.count},对比sleep前值={num}:{s}')Lock.release() # 释放线程锁i+=1 i+=1count+=1# 自定义的函数,供线程2调用def task4(self, number, id):count=1i=1m = 1while i <= number: Lock.acquire() # 设置线程锁myThread2.lst[1]=i myThread2.lst[3]=count myThread2.count+=1num=myThread2.counts=''sleep(0.01) #因休眠期,可能存在其他跑得快的线程将此全局变量改了,即myThread.count不一定是连续的if(myThread2.count!=num):s='两值已不相同'print(f'\ntask4:线程{id}中lst={myThread2.lst},线程类全局计数count={myThread2.count},对比sleep前值={num}:{s}')Lock.release() # 释放线程锁i+=1count+=1if __name__ == '__main__':print('开始示例1:开启两个线程,不锁线程......')starttime = timer()thread1 = myThread1(80, 1) # 创建线程thread1:thread2 = myThread1(150, 2) # 创建线程thread2:thread1.start() # 启动线程1thread2.start() # 启动线程2thread1.join() # 等待线程1thread2.join() # 等待线程2endtime = timer()sumtime=(endtime-starttime)*1000print(f'示例1运行总时间{sumtime}毫秒\n\n5秒后准备开始运行示列2:有锁线程.....')sleep(5)print('开始示例2:开启两个线程,锁线程......')starttime = timer()thread3 = myThread2(80, 1) # 创建线程thread3:thread4 = myThread2(150, 2) # 创建线程thread4:thread3.start() # 启动线程3thread4.start() # 启动线程4thread3.join() # 等待线程3thread4.join() # 等待线程4endtime = timer()sumtime=(endtime-starttime)*1000print(f'示例2运行总时间{sumtime}毫秒')
3、第三种使用多线程的方式是,先建立一管理多线程,此多线程负责管理其他新建的工作用多线程,管理线程中是靠一字典变量来保存和工作线程对象,接着再建立若干工作多线程,用这些线程来完成计算等工作任务,在管理线程中来打开或关闭工作线程。
示例代码运行界面
#模块功能:基于Python的多线程类的示例:演示多线程管理类trdManager和多线程类trdWork及其子类trdWork_03....等来执行多线程数据计算,
# 用于将大量复杂的数据计算包装到多线程类中来完成
# 线程1和线程2演示多线程同主窗口数据通信,线程3演示用继承多线程子类trdWork_03执行另一种计算任务
# 一个线程类可初始化多个对象,对相同计算功能的可用一个同名线程类定义多个实例化线程对象,
# 每个线程对象执行相同的计算功能完成对拆分的大数据分块计算,不同计算功能的可分别继承定义不同功能的线程子类并实例化对象
import sys,time,random,math
from PySide6 import *
from PySide6.QtWidgets import *
from PySide6.QtCore import *
#from PySide6.QtGui import * #如果运行时没有任何界面调出,也不报错,请屏蔽此行,原因不详
import PySide6.QtChartsfrom PySide6.QtCore import Signal, QEvent,Property, QSize
from PySide6.QtCore import (QDateTime, QFile,QDir, QLibraryInfo, QSysInfo, Qt,QTimer,Slot, QAbstractTableModel, QModelIndex,QPoint,QPointF,QStandardPaths, QUrl, QIODevice, QRectF,qFatal,qWarning,qVersion)
from PySide6.QtGui import (QCursor,QIcon,QImage,QPicture,QDesktopServices, QGuiApplication,QKeySequence, QShortcut, QStandardItem,QStandardItemModel)
from PySide6.QtGui import (QPen,QBrush,QColor,QFont, QPainter,QGradient,QMatrix4x4,QPlatformSurfaceEvent, QSurface, QWindow,QSurfaceFormat)
from PySide6.QtGui import (QRhi, QRhiBuffer,QPixmap,QAction,QWheelEvent,QRhiDepthStencilClearValue,QRhiGraphicsPipeline, QRhiNullInitParams,QRhiGles2InitParams, QRhiRenderBuffer,QRhiSampler, QRhiShaderResourceBinding,QRhiShaderStage, QRhiTexture,QMovie,QRhiVertexInputAttribute, QRhiVertexInputBinding,QRhiVertexInputLayout, QRhiViewport, QShader)
from PySide6.QtWidgets import (QApplication, QDialog,QWidget, QFileDialog, QMainWindow, QMessageBox)
from PySide6.QtWidgets import (QCheckBox, QComboBox,QCommandLinkButton, QDateTimeEdit, QDial,QDialog, QDialogButtonBox, QFileSystemModel,QGridLayout, QGroupBox, QHBoxLayout, QLabel,QLineEdit, QListView, QMenu, QPlainTextEdit,QProgressBar, QPushButton, QRadioButton,QScrollBar, QSizePolicy, QSlider, QSpinBox,QStyleFactory, QTableWidget, QTabWidget,QTextBrowser, QTextEdit, QToolBox, QToolButton,QTreeView, QVBoxLayout)import threading #Py的多线程类
from math import *#基于QT的演示窗口类
class MainWindow(QWidget):def __init__(self, parent=None):super(MainWindow, self).__init__(parent)global bTrd00, bTrd01self.setWindowTitle('多线程(同时开4个)示例')self.resize(900,500)self.startTime5=self.endTime5=0.0 self.endTime6=self.endTime6=0.0 layout = QFormLayout() self.pushButton_1 = QPushButton()self.pushButton_1.setText("开始新线程")self.pushButton_2 = QPushButton()self.pushButton_2.setText("开始新线程2")self.pushButton_3 = QPushButton()self.pushButton_3.setText("开始新线程3")self.pushButton_4 = QPushButton()self.pushButton_4.setText("停止线程1")self.pushButton_5 = QPushButton()self.pushButton_5.setText("停止线程2")self.pushButton_6 = QPushButton()self.pushButton_6.setText("停止线程3")self.edita1 = QLineEdit()self.edita1.setText('信号槽:线程1+2的值')self.progressBar_1=QProgressBar()self.progressBar_1.setMinimum(0)self.progressBar_1.setMaximum(100)self.edita2 = QLineEdit()self.edita2.setText('信号槽:线程3+4的值')self.progressBar_2=QProgressBar()self.progressBar_2.setMinimum(0)self.progressBar_2.setMaximum(100)self.edita3 = QLineEdit()self.edita3.setText('信号槽:线程5的值')layout.addRow("单击按纽开始线程1(线程sleep 500毫秒)->",self.pushButton_1)layout.addRow("单击按纽结束线程1->",self.pushButton_4)layout.addRow("计时器不间断查询返回线程1的值->",self.edita1)layout.addRow("线程1返回值(进度条展示)->",self.progressBar_1)layout.addRow("单击按纽开始线程2(线程sleep 20毫秒)->",self.pushButton_2)layout.addRow("单击按纽结束线程2->",self.pushButton_5)layout.addRow("计时器不间断查询返回线程2的值->",self.edita2)layout.addRow("线程2返回值(进度条展示)->",self.progressBar_2)layout.addRow("单击按纽开始线程3->",self.pushButton_3)layout.addRow("单击按纽结束线程3->",self.pushButton_6)layout.addRow("计时器不间断查询得到的线程3计算结果->",self.edita3)self.setLayout(layout)self.pushButton_4.setEnabled(False)self.pushButton_5.setEnabled(False)self.pushButton_6.setEnabled(False)#定义线程管理器self.trd_manages=trdManager()self.trdWork_1=trdWork(1,'线程1',0.5) self.trdWork_2=trdWork(2,'线程2',0.02)self.trdWork_3=trdWork_03(3,'线程3',0.001) #继承自trdWork的另一不同的run()计算代码类self.pushButton_1.clicked.connect(self.start_worker_1)self.pushButton_2.clicked.connect(self.start_worker_2)self.pushButton_3.clicked.connect(self.start_worker_3)self.pushButton_4.clicked.connect(self.stop_worker_1)self.pushButton_5.clicked.connect(self.stop_worker_2)self.pushButton_6.clicked.connect(self.stop_worker_3)# 初始化一个QT定时器1,对线程1和线程2实时查询数据计算结果self.timerID1 = QTimer(self)self.timerID1.start(20)# 将定时器与槽函数qtTimeID1连接self.timerID1.timeout.connect(self.qtTimeID1) # 初始化一个QT定时器2,对线程3实时查询数据计算结果self.timerID3 = QTimer(self)self.qtStartTimerID3(0.1)# 将定时器与槽函数qtTimeID3连接self.timerID3.timeout.connect(self.qtTimeID3) #QT计时器函数体: 每隔指定时间查询多线程中的计算完成情况def qtTimeID1(self):if(self.trdWork_1.is_running):count1=self.trdWork_1.testCountcount2=self.trdWork_2.testCountself.edita1.setText(f'工作线程self.trdWork_1中的值为={count1}')self.edita2.setText(f'工作线程self.trdWork_2中的值为={count2}')self.progressBar_1.setValue(count1)self.progressBar_2.setValue(count2)def qtEndTimerID1(self):if(self.timerID1.isActive()):self.timerID1.stop()#开始QT类计时器,计时器间隔时间1000毫秒def qtStartTimerID3(self,timeSleep):if(self.timerID3.isActive()):self.timerID3.stop()self.timerID3.start(timeSleep*20)#QT计时器函数体: 每隔指定时间查询多线程中的计算完成情况def qtTimeID3(self):if(self.trdWork_3.is_running and (not self.trdWork_3.is_workOK)):self.edita3.setText(f'工作线程self.trdWork_3中当前正在计算,当前计算值={self.trdWork_3.testCount}')elif(self.trdWork_3.is_workOK):self.edita3.setText(f'工作线程self.trdWork_3已完成全部计算,最终计算结果={self.trdWork_3.testCount}')def qtEndTimerID3(self):if(self.timerID3.isActive()):self.timerID3.stop()#点击按纽1的信号槽:开始线程1def start_worker_1(self):self.pushButton_1.setEnabled(False)self.pushButton_4.setEnabled(True)self.trd_manages.start_thread(self.trdWork_1) #开始工作线程1self.trdWork_1.is_running=True#点击按纽2的信号槽:开始线程3+接收线程4def start_worker_2(self):self.pushButton_2.setEnabled(False)self.pushButton_5.setEnabled(True)self.trd_manages.start_thread(self.trdWork_2) #开始工作线程2 #点击按纽3的信号槽:开始线程3,用于计算def start_worker_3(self):self.pushButton_3.setEnabled(False)self.pushButton_6.setEnabled(True)self.trd_manages.start_thread(self.trdWork_3) #开始工作线程3self.startTime3=time.time() #记录线程3从开始到计算完成用的时间self.edita3.setText('开始循环20000000次计算一复杂计算式,请等待。。。')#点击按纽4的信号槽:结束线程1def stop_worker_1(self):self.pushButton_1.setEnabled(True)self.pushButton_4.setEnabled(False)#self.trd_manages.stop_threadID(1)self.trd_manages.stop_thread(self.trdWork_1)#点击按纽5的信号槽:结束线程2def stop_worker_2(self):self.pushButton_2.setEnabled(True)self.pushButton_5.setEnabled(False)self.trd_manages.stop_thread(self.trdWork_2) #点击按纽6的信号槽:结束线程5def stop_worker_3(self):self.pushButton_3.setEnabled(True)self.pushButton_6.setEnabled(False)self.trd_manages.stop_thread(self.trdWork_3) #线程中直接调用主窗口句柄对象对应的方法:此方法不建议采用,可能频繁调用会造成运行错误 def threadToWindow(self,putID,reValue):pass
#*************************************************************************************************
#定义工作线程类
class trdWork():def __init__(self,ID,description,timeSleep=0.5):self.description=description #线程名称self.ID=ID #线程ID号,同列表下标一致 self.testCount=0 #测试用自增1变量 print(description)self.is_workOK=False #本线程工作是否全部完成self.is_running=True #控制线程的循环体,为False时会退出循环体到run函数体尾部,表示本线程工作完成self.timeSleep=timeSleep #为防系统卡顿,设置循环体中间隔休眠时间#线程工作函数,本函数执行到最后一行代码视作本线程工作完成def run(self):while self.is_running:self.testCount+=1if(self.testCount>100):self.testCount=0#print(f'工作线程ID={self.ID},run函数循环体内执行计数={self.testCount}\n')time.sleep(self.timeSleep)self.is_workOK=True #本工作线程已全部完成计算工作print(f'线程ID={self.ID}run函数体工作已全部完成')#停止线程def stop(self,bRust=True):if(not self.is_workOK):print(f'线程还未完成任务,如强行停止,可能会造成计算错误')if(bRust):self.is_running=Falseelse:if(bRust):self.is_running=False#定义工作线程类
class trdWork_03(trdWork):def __init__(self,ID,description,timeSleep=0.5):super().__init__(ID,description,timeSleep)#重载run,以实现另一种计算过程,重载后父类中的run不会再被调用def run(self):loopNum=20000000for i in range(loopNum):if(self.is_running):self.testCount+=sqrt(sqrt(sqrt(sqrt(i**1.23456789**0.975312468**0.05642317895412)*sin(i**0.123456789/pi)*sqrt(i**0.0123456789**0.13579**0.2468/pi))))else:print(f'线程{self.ID}被强行停止,但当前的计算工作还没有完成')self.is_workOK=Truereturn self.is_workOK=True #本工作线程已全部完成计算工作print(f'线程ID= {self.ID} run函数体工作已全部完成')#自定义计算用多线程管理类(每start_thread方法一次就会增加一个线程)
class trdManager():def __init__(self):self.dicthreads={} #以字典方式记录各线程,其中字典key=ID#开始一个线程(本类每调用一次增加一个线程,可同ID号,会覆盖此ID线程)def start_thread(self,task):newthread=threading.Thread(target=task.run)ID=task.IDif(self.dicthreads.get(ID,None)!=None):print(f'多线程ID={ID}曾经被创建过或正在运行中。。。')raise customExcept(0) #抛出异常self.dicthreads[ID]=(newthread,task)print(f'\n************新线程的self.ID={ID}************')newthread.start()#关闭指定ID号的线程 def stop_threadID(self,ID): trds=self.dicthreads.get(ID,None)curThread=trds[0]task=trds[1]if(curThread!=None):curThread.stop()task.join()print(f'\n线程ID={curThread.ID}被停止')#关闭指定的线程 def stop_thread(self,thread): for key in self.dicthreads:curThread=self.dicthreads[key][0]dask=self.dicthreads[key][1]if(thread==dask):thread.stop()dask.join()print(f'\n线程ID={curThread.ID}被停止')#得到指定名称的线程的IDdef getID(self,task):for key in self.dicthreads.values:pass#自定义异常类,在使用代码处如认为出现异常,用代码raise customExcept('自定义异常,ID=1',1) ,异常处thy:.... except customExcept as e: print(e.msg)
class customExcept(Exception):def __init__(self,msg,ID=0):self.msg=msgif(ID==0):self.msg='自定义异常1:多线程ID曾经被创建过或正在运行中。。。,新线程ID可能会同正在运行的线程ID号冲突'elif(ID==1):self.msg='自定义异常2'#########################################################################################app = QApplication([])mainWin = MainWindow()mainWin.show()app.exec()
相关文章:

PySide6学习专栏(四):用多线程完成复杂计算任务
如果计程序中要处理一个非常庞大的数据集中的数据,且数据处理计算很复杂,造成数据处理占用大量时间和CPU资源,如果不用多线程,仅在主进程中来处理数据,将会使整个程序卡死,必须采用多线程来处理这些数据是唯…...

Python多线程编程理解面试题解析
一、多线程介绍 Python 的多线程是一种实现并发编程的方式,允许程序同时执行多个任务。然而,由于 Python 的全局解释器锁(GIL)的存在,多线程在某些场景下可能无法充分利用多核 CPU 的性能。以下是对 Python 多线程的理…...

Flutter - 初体验
项目文件目录结构介绍 注:创建 Flutter 项目名称不要包含特殊字符,不要使用驼峰标识 // TODO 开发中运行一个 Flutter 三种启动方式 Run 冷启动从零开始启动Hot Reload 热重载执行 build 方法Hot Restart 热重启重新运行整个 APP 先看效果,…...
使用最广泛的Web应用架构
目前互联网中没有一种绝对使用最广泛的Web应用架构,不同的架构在不同的场景和企业中都有广泛应用,但微服务架构和Serverless架构是当前较为主流和广泛使用的架构。以下是对这两种架构的具体分析: 微服务架构 适用场景广泛 大型互联网公司&a…...
YOLOv11-ultralytics-8.3.67部分代码阅读笔记-split_dota.py
split_dota.py ultralytics\data\split_dota.py 目录 split_dota.py 1.所需的库和模块 2.def bbox_iof(polygon1, bbox2, eps1e-6): 3.def load_yolo_dota(data_root, split"train"): 4.def get_windows(im_size, crop_sizes(1024,), gaps(200,), im_rate_t…...

Unity shader glsl着色器特效之 模拟海面海浪效果
一个简单的海浪效果,通过波的叠加实现水面起伏的动效,根据波峰斜率来为浪花着色,再根据法线贴图和水花贴图来和调整uv的平滑移动来增强海浪移动的细节。如果需要更逼真的效果可以考虑在满足浪花触发的地方添加粒子系统 前置效果图 因为是很久…...

`AdminAdminDTO` 和 `userSession` 对象中的字段对应起来的表格
以下是将更正后的表格放在最前面的回答,表格包含序号列,合并了后端 AdminAdminDTO 和前端 userSession 的所有字段,并标注对方没有的字段。token 字段值用省略号(...)表示: 序号字段名AdminAdminDTO (后端…...
sqlserver查询内存使用情况的方法
查询 这个SQL查询用于获取当前数据库实例中各个数据库在缓冲池(buffer pool)中的数据页所占用的内存大小。 select isnull(db_name(database_id),ResourceDb) AS DatabaseName,CAST(COUNT(row_count) * 8.0 /(1024.0) AS DECIMAL(28,2)) AS [size (MB…...
rust笔记7-生命周期显式标注
Rust 的生命周期(Lifetimes)是 Rust 内存安全模型的核心部分,用于确保引用始终有效,避免悬垂引用(Dangling References)。下面我们从生命周期的设计出发点、标注语法以及在不同上下文中的应用(函数、方法、结构体、trait 等)来详细介绍。 1. 生命周期设计的出发点 Rus…...

SQL Server 导入Excel数据
1、选中指定要导入到哪个数据库,右键选择 》任务 》导入数据 2、数据源 选择Excel,点击 下一步(Next) 3、目前 选择OLE DB Provider ,点击 下一步(Next) 4、默认 ,点击 下一步(Next)…...

【笔记】LLM|Ubuntu22服务器极简本地部署DeepSeek+联网使用方式
2025/02/18说明:2月18日~2月20日是2024年度博客之星投票时间,走过路过可以帮忙点点投票吗?我想要前一百的实体证书,经过我严密的计算只要再拿到60票就稳了。一人可能会有多票,Thanks♪(・ω・)&am…...
【面试题】2025.02.19-前端面试题汇总
杭州三汇 1. 自我介绍 2. 你们前端项目为什么要用微前端? 减少由于程序更新导致的问题影响面积;缩小前端包体积,加快页面开发速度;便于统一多家医院某几个系统的程序一直; 3. 详细介绍一个项目,项目干什…...

小米AX3000T 路由器如何开启 SSH 安装 OpenWRT 系统,不需要降级 v1.0.91 (2025)
小米AX3000T 路由器如何开启 SSH 安装 OpenWRT 系统,不需要降级 v1.0.91 (2025) 本文内容需要你有一定的 Linux 操作基础,最好是程序员那种,英文水平足够用才行。一般人不需要使用这么复杂的路由器操作系统,…...

火语言RPA--Excel插入空行
【组件功能】:在Excel内指定的位置插入空行 配置预览 配置说明 在第n行之前 支持T或# 填写添加插入第n行之前行号。 插入n行 支持T或# 插入多少行。 Sheet页名称 支持T或# Excel表格工作簿名称。 示例 Excel插入空行 描述 在第3行之后插入3行。 配置 输…...

具有整合各亚专科医学领域知识能力的AI智能体开发纲要(2025版)
整合各亚专科医学领域知识能力的AI代理的开发与研究 一、引言 1.1 研究背景 在科技飞速发展的当下,人工智能(AI)已成为推动各行业变革的关键力量,医疗领域也不例外。近年来,AI 在医疗行业的应用取得了显著进展,从医学影像诊断到疾病预测,从药物研发到个性化医疗,AI 技…...

【Java 优选算法】位运算
欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,欢迎指出~ 基础位运算符: &: 有 0 就是 0 | : 有 1 就是 1 ^ :相同为0,相异为1(无进位相加) 1.给一个数 n, 确定它的二进制表示中的第x位是 0 还是 1 . 使用公式(n >> x) &…...
细分数字货币钱包的不同种类
文章目录 一、中心化钱包1.1 中心化钱包架构1.2 中心化钱包业务细节流程 二、去中心化钱包(HD 钱包)2.1 去中心化钱包架构2.2 去中心化钱包细节业务流程 三、硬件钱包3.1 硬件钱包架构3.2 硬件钱包细节业务流程 四、MPC 托管钱包五、多签钱包 中心化钱包 :钱包私钥一…...
Nginx Embedded Variables 嵌入式变量解析(4)
Nginx Embedded Variables 嵌入式变量解析(4) 相关链接 nginx 嵌入式变量解析目录nginx 嵌入式变量全目录nginx 指令模块目录nginx 指令全目录 一、目录 1.1 变量目录 1.1.24 ngx_stream_core_module $binary_remote_addr $bytes_received $bytes_sent $connection $hos…...

ARM64 Trust Firmware [四]
完成第二阶段 BL2 的操作后就加载并进入 BL31,BL31 位于 DRAM 中,EL3 模式。除了做架构初始化和平台初始化外,还做了如下工作: 基本硬件初始化,比如 GIC,串口,timer 等;PSCI 服务的…...

SQLMesh 系列教程6- 详解 Python 模型
本文将介绍 SQLMesh 的 Python 模型,探讨其定义、优势及在企业业务场景中的应用。SQLMesh 不仅支持 SQL 模型,还允许通过 Python 编写数据模型,提供更高的灵活性和可编程性。我们将通过一个电商平台的实例,展示如何使用 Python 模…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...