在Python3中,推荐使用threading而不是thread,原thread已废(通过import _thread可兼容访问,但不推荐使用,毕竟官方抛弃了。。。)
在编写UI界面时,必然会遇到多线程问题:比如背景循环刷数据,通讯帧处理等。在PyQT中,提供了QThread类,在讲这个类之前,先看Python原生的线程类及其使用
import threading
import time
class myThread(threading.Thread):
def __init__(self,threadID,name,counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print('start thread' + self.name + time.ctime(time.time()))
print_time(self.name,self.counter)
print('exit time'+ self.name + time.ctime(time.time()))
def print_time(threadName,counter):
while counter:
time.sleep(1)
print(threadName,time.ctime(time.time()))
counter -= 1
if __name__ == "__main__":
thread1 = myThread(1,'thread1',2)
thread2 = myThread(2,'thread2',4)
thread1.start()
thread2.start()
print('exit main' + time.ctime(time.time()))
Connected to pydev debugger (build 183.4284.139)
start threadthread1Mon Dec 3 19:38:25 2018
start threadthread2Mon Dec 3 19:38:25 2018
exit mainMon Dec 3 19:38:25 2018
thread1 Mon Dec 3 19:38:26 2018
thread2 Mon Dec 3 19:38:26 2018
thread1 Mon Dec 3 19:38:27 2018
exit timethread1Mon Dec 3 19:38:27 2018
thread2 Mon Dec 3 19:38:27 2018
thread2 Mon Dec 3 19:38:28 2018
thread2 Mon Dec 3 19:38:29 2018
exit timethread2Mon Dec 3 19:38:29 2018
Process finished with exit code -1
0.首先要说的是,从
if __name__ == "__main__":
开始,是主线程的开始
.
1.myThread类继承自threading类,包含构造函数和run函数,
.
2.构造函数用于内部参数初始化,run函数用于启动线程
.
3.main函数新建两个myThread实例,分别执行对象的start方法,启动线程(调用run)
.
4.着重是run函数,run函数循环执行print_time,次数由counter指定,计数结束即退出线程。因此,如果需要该线程一直执行,应该在run内部设置死循环,比如刷新界面时间
主线程并没有等子线程结束,就自行退出了,我们如果想主线程大哥最后撤退,做一些收尾工作,该怎么办?
join ()方法:主线程中,创建了子线程a,并且在主线程中调用了a.join(),那么,主线程会在调用的地方等待,直到子线程a执行完成后,再接着往下执行。
原型:join([timeout])
timeout参数是可选的,代表该线程运行的最大时间。即如果超过这个时间,主线程就不等了,自己先走了。最后就剩那些超时的线程在那里墨迹,最后一个线程墨迹完了,程序也就结束了。当然,你可以说大哥必须等,那timeout缺省就行了,大哥一直等到大家忙完,再忙自己的。
可以以上面的代码举例:
在线程开启后,添加join
thread1.start()
thread2.start()
thread1.join()
thread2.join()
输出结果:
Connected to pydev debugger (build 183.4284.139)
start threadthread1Mon Dec 3 20:04:26 2018
start threadthread2Mon Dec 3 20:04:26 2018
thread1 Mon Dec 3 20:04:27 2018
thread2 Mon Dec 3 20:04:27 2018
thread1 Mon Dec 3 20:04:28 2018
exit timethread1Mon Dec 3 20:04:28 2018
thread2 Mon Dec 3 20:04:28 2018
thread2 Mon Dec 3 20:04:29 2018
thread2 Mon Dec 3 20:04:30 2018
exit timethread2Mon Dec 3 20:04:30 2018
exit mainMon Dec 3 20:04:30 2018
Process finished with exit code -1
可以看到,大哥主线程一直在等子线程1 子线程2执行完毕才执行,最后打印exit mainMon Dec 3 20:04:30 2018
退出
继续举这个例子,把thread1.join()
改成thread1.join(1)
,即主线程只等你1秒,还等不到就先跑路了
看运行结果
Connected to pydev debugger (build 183.4284.139)
start threadthread1Mon Dec 3 20:10:26 2018
start threadthread2Mon Dec 3 20:10:26 2018
thread2 Mon Dec 3 20:10:27 2018
thread1 Mon Dec 3 20:10:27 2018
thread1 Mon Dec 3 20:10:28 2018
exit timethread1Mon Dec 3 20:10:28 2018
thread2 Mon Dec 3 20:10:28 2018
thread2 Mon Dec 3 20:10:29 2018
thread2 Mon Dec 3 20:10:30 2018
exit timethread2Mon Dec 3 20:10:30 2018
exit mainMon Dec 3 20:10:30 2018
Process finished with exit code -1
可是,主线程还是最后才退出,这是为什么?因为,虽然子线程1总共要运行2s,主线程只等你1s,但主线程还要等子线程2,而且是无限等。子线程2执行4s,然后主线程开始执行,此时子线程1早已收工。因此,最后是主线程输出。
所以,如果把thread2.join()
改成thread2.join(1)
,输出log如下:
Connected to pydev debugger (build 183.4284.139)
start threadthread1Mon Dec 3 20:16:54 2018
start threadthread2Mon Dec 3 20:16:54 2018
thread2 Mon Dec 3 20:16:55 2018
thread1 Mon Dec 3 20:16:55 2018
thread1 Mon Dec 3 20:16:56 2018
exit timethread1Mon Dec 3 20:16:56 2018
thread2 Mon Dec 3 20:16:56 2018
exit mainMon Dec 3 20:16:57 2018
thread2 Mon Dec 3 20:16:57 2018
thread2 Mon Dec 3 20:16:58 2018
exit timethread2Mon Dec 3 20:16:58 2018
Process finished with exit code 0
主线程无限等子线程1结束,再等子线程2,最多等1s还墨迹也不等了,主线程先撤。最后子线程2忙完了,程序也就结束了。
join的原理就是依次检验线程池中的线程是否结束,没有结束就阻塞主线程直到子线程结束。如果子线程结束,则执行下一个线程的join函数
如果从程序设计的角度看,主线程应该是程序的核心,主线程结束了,子线程也应该相继结束,这该如何实现?
当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则子线程全部被终止执行
但是,也会带来负面影响:可能出现的情况是,子线程的任务还没有完全执行完毕,就被迫停止
join有一个timeout参数:
如前所述:
如果没有设置子线程为守护线程时,主线程会等待子线程timeout时间,超时了,主线程往下执行直至结束。但是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。
如果设置子线程为守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。总结来看:时间一到,不管子线程有没有完成,直接杀死。
以刚刚最后的情形举例:
import threading
import time
class myThread(threading.Thread):
def __init__(self,threadID,name,counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self):
print('start thread' + self.name + time.ctime(time.time()))
print_time(self.name,self.counter)
print('exit time'+ self.name + time.ctime(time.time()))
def print_time(threadName,counter):
while counter:
time.sleep(1)
print(threadName,time.ctime(time.time()))
counter -= 1
if __name__ == "__main__":
thread1 = myThread(1,'thread1',2)
thread2 = myThread(2,'thread2',4)
thread1.setDaemon(True)
thread2.setDaemon(True)
thread1.start()
thread2.start()
thread1.join()
thread2.join(1)
print('exit main ' + time.ctime(time.time()))
输出结果:
Connected to pydev debugger (build 183.4284.139)
start threadthread1Mon Dec 3 20:56:42 2018
start threadthread2Mon Dec 3 20:56:42 2018
thread2 Mon Dec 3 20:56:43 2018
thread1 Mon Dec 3 20:56:43 2018
thread1 Mon Dec 3 20:56:44 2018
exit timethread1Mon Dec 3 20:56:44 2018
thread2 Mon Dec 3 20:56:44 2018
exit main Mon Dec 3 20:56:45 2018
Process finished with exit code 0
主线程只等子线程2一秒,还在墨迹,直接kill掉,然后退出
到这里,Python的多线程算是告一段落,下面讲一下PyQT的多线程应用
通常,我们会通过继承QThread,重写run方法,来实现业务需求
比如:
class MBThread(QThread):
oneSecondTriger = pyqtSignal()
def __init__(self):
super(MBThread,self).__init__()
def run(self):
while True:
self.oneSecondTriger.emit()
time.sleep(1)
我定义了一个MBThread类,继承自QThread,在run函数内,每隔1s触发一个信号
而在另一个类MyWindow中,我定义了MBThread的实例,并绑定了槽函数。代码如下:
class MyWindow(QMainWindow,Ui_MainWindow):
def __init__(self):
super(MyWindow,self).__init__()
self.timeThread = MBThread()
self.timeThread.oneSecondTriger.connect(self.timeUpdate)
self.timeThread.start()
def timeUpdate(self):
self.label_Time.setText(time.strftime('%H:%M:%S'))
try:
x = self.MBusMaster.execute(35, cst.READ_HOLDING_REGISTERS, 4, 2)
self.rh = AddrIntToFloat(x[0], x[1])
x = self.MBusMaster.execute(35, cst.READ_HOLDING_REGISTERS, 5, 2)
self.t = AddrIntToFloat(x[0], x[1])
except modbus_tk.modbus_rtu.ModbusInvalidResponseError as err:
print(err)
self.label_RHValue.setText(str(round(self.rh,2)))
self.label_TValue.setText(str(round(self.t,2)))
为了方便阅读,上段代码做了一些删除,能表达清楚意思即可。槽函数timeUpdate在新的类中MyWindow定义,每次信号触发都将调用该函数。同样地,UI当中的时间标签label_Time也会在每次触发调用后,更新界面时间。
多余的话:上述代码来自我的一个小项目,应用了Python的Modbus库,在线程槽函数timeUpdate内,改成你需要的业务处理就行了。
好,本篇结束