Python 多个线程按先后顺序执行,并保持各子线程和主线程的通信
摘要
最近有个项目使用pyqt5写的界面,界面展示部分作为项目的主线程,另外通过调用Thread,传入不同的参数又设置了五个子线程,因为第一个子线程的运算结果是第二个线程的基础,而前两个线程又是后面三个子线程的基础。因此需要按顺序先后执行第一、第二、第三四五个线程,并且要求子线程在执行过程中的运算结果传递到主线程的界面中显示出来。
一,实现方法
1,遇到问题
为了实现上述的功能要求尝试使用了线程的join()函数即在每个子线程开启之后立即使用join(),这样各子线程就会按先后顺序执行,但join()会阻塞主线程,对子线程执行join(),主线程阻塞等待子线程完全执行完毕后才会继续主线程,因此由子线程在运行过程中产生的返回到主线程的返回值也不会显示出来,无法刷新界面。
2,解决方法
为了实现上述的功能要求,并避免子线程和主线程的通信不同步,查找网上的解决办法,发现threading模块的的Timer()函数可以有效解决这个问题。
- Threading.Timer()
Timer()是继承自Thread的子类,源码如下
class Timer(Thread): # 继承自Thread类
"""Call a function after a specified number of seconds:
t = Timer(30.0, f, args=None, kwargs=None)
t.start()
t.cancel() # stop the timer's action if it's still waiting
"""
def __init__(self, interval, function, args=None, kwargs=None): # 初始化的时候传参是延迟时间、调用的函数,函数的可变位置参数、函数的可变关键字参数
Thread.__init__(self) # 调用Thread类初始化配置实例
self.interval = interval # 在使用Thread类初始化配置实例之后再额外的增加interval属性
self.function = function # 同理再额外的增加function属性
self.args = args if args is not None else [] # 如果args不是空的话就使用args,如果是空就给一个空list
self.kwargs = kwargs if kwargs is not None else {} # 同理,kwargs不是空的就是kwargs,如果是空就给一个空字典
self.finished = Event() # 再添加一个属性finished,是一个Event类的实例,这里知道Event类的实例用法就知道它在这里要怎么用了
def cancel(self): """如果finished属性还没有被set,即函数function还没有被调用的之前阻止,因为下面函数调用之前会判断finished是否被set了,所以这里赶在调用之前注定set就能够阻止后面的调用。"""
"""Stop the timer if it hasn't finished yet."""
self.finished.set()
def run(self): # 继承自Thread类,并且重写了Thread类,我们分析Thread类的源码会发现,start()方法会主动调用self.run(), # 我们Timer类没有实现start()方法,这样Timer类实例在执行start()的时候会跑到父类Thread上,然后调用父类的start, # 在父类的start()方法中会有一句self.run()来调用工作线程中的函数,这里self是Timer的实例,所以,这里可以重写run就可以设定run的时间了。
self.finished.wait(self.interval) # 这里使用Event类的实例的wait方法,等待了我们设定的self.interval时间,然后关键点是下面一句
if not self.finished.is_set(): # 这一句是关键点,检查一下是否被set了,如果没有被set了就调用传入的函数,如果被set了有两种情况: # 第一种情况是在self.finished.wait(self.interval)的期间,我们调用cancel主动提前set了; # 第二种情况是已经start()过一次了,这里就不能再进行start了这样就和父类的保持了一致:即一个线程只能够start一次
self.function(*self.args, **self.kwargs)
self.finished.set() # 调用完成后set,即便之前已经被set了,这里还可以被set,因为Event实例可以被set多次。
3,使用实例
def DetectThread(Id):
global timer
if Id == 0:
t1 = time.time()
time_local = time.localtime(t1)
dt = time.strftime("%Y.%m.%d %H:%M:%S", time_local)
print('执行线程1,执行时间:', dt)
timer = threading.Timer(2, DetectThread, (1,))
timer.start()
if Id == 1:
t2 = time.time()
time_local = time.localtime(t2)
dt = time.strftime("%Y.%m.%d %H:%M:%S", time_local)
print('执行线程2,执行时间:', dt)
timer = threading.Timer(2, DetectThread, (2,))
timer.start()
if Id == 2:
time_local = time.localtime(time.time())
dt = time.strftime("%Y.%m.%d %H:%M:%S", time_local)
print('执行线程3,执行时间:', dt)
timer = threading.Timer(2, DetectThread, ('里程桩',))
timer.start()
- 输出
D:\Anaconda\anaonda3\envs\tensorflow2-gpu\python.exe D:/models-master/research/object_detection/多线程按顺序执行测试.py
执行线程1,执行时间: 2021.01.18 13:14:11
执行线程2,执行时间: 2021.01.18 13:14:13
执行线程3,执行时间: 2021.01.18 13:14:15
Process finished with exit code 0