Python 在使用多线程时,要实现同时并发运行线程,一般都会用 Queue 队列来实现,但一用到 Queue 就难于控制线程与GUI 界面上的信息交互,特别是在用 tkinter 界面时,由于 tykinter 对线程安全并不友好,很容易出现卡壳情况。
简单的多线程模型
这里要实现的多线程模型仅用 threading 模块,这里先介绍网上最容易找到的实例,并进行说明其优缺点,后面再给出真正能并发多线程的实例。
下面多线程的例子随便在网上就能搜出一堆来
import threading
import time,random
class myThread(threading.Thread):#线程类
def __init__(self,index,threadIndex):
super().__init__()
self.index=index
self.threadIndex=threadIndex
def run(self):
print("线程【{}】:任务:{} ——开始".format(self.threadIndex,self.index))
self.task()
print("线程【{}】:任务:{} ——结束".format(self.threadIndex,self.index))
def task(self):
time.sleep( random.randint(1,50)/10)
def main():
print("【开始所有任务】")
count=50 #任务总数
threadCount=10 #线程数
index=0 #任务序号
while count >index:
threads=[] #线程列表
for i in range(threadCount):
t=myThread(index,i)
threads.append(t)
index+=1
if count<=index:
break
for t in threads: #创建线程后统一开启
t.start()
for t in threads: #全部开启后统一进行阻塞
t.join()
print("【结束所有任务】")
if __name__ == '__main__':
main()
上面为最简单的多线程模型,加入线程阻塞后能在所有线程结束处理一些事情。它的优点是简单易懂,可以对界面进行简单的信息交互;缺点是不是真正意义上的同时并发,而是按线程数进行分批次的并发。即按上例为将50个任务按每次10个分成5批,每批任务全部执行完后,才进入下一批任务,不能持续同时执行10个任务,因此不是真正意义上的并发多线模型。
并发执行的多线程模型
请耐心看完下面的说明后,才好理解后面的代码
要实现持续同时有10个任务在运行,需要实时判断是否有任务结束,结束后再立即创建新的线程来补充。要实时监控是否有任务结束,只靠线程的 join()方法无法做到及时监控,因此需要自制任务的阻塞机制,同时监控线程是否结束。
任务开始时创建的线程需要全部创建完成后,再统一开始执行,而后面结束任务后创建的线程则需要立即执行。
要替换已完成的线程,需要准确地区分是哪个线程结束,所以在创建线程列表时,还要加入线程号的概念,后面再根据结束的线程号替换到列表中的线程。完整的代码见下
import threading
import time,random
class myThread(threading.Thread):#线程类
def __init__(self,index,threadIndex):
super().__init__()
self.index=index
self.threadIndex=threadIndex
self.isFinish=False
def run(self):
print("线程【{}】:任务:{} ——开始".format(self.threadIndex,self.index))
self.task()
print("线程【{}】:任务:{} ——结束".format(self.threadIndex,self.index))
self.isFinish=True #线程结束
def task(self):
time.sleep( random.randint(1,50)/10)
def main():
print("【开始所有任务】")
count=50 #任务总数
threadCount=10 #线程数
def getThreadIndex(threads,threadIndex):#根据线程号判断线程是否存在
for i in range(len(threads)):
if threads[i][0]==threadIndex:
return i #返回线程列表中的序号(index)
return -1 #线程不存在,可以创建
#
def addThread(threads,threadIndex,index,start=0):#创建线程
local= getThreadIndex(threads,threadIndex)
if local>-1:
threads.pop(local) #删除原线程列表中已结事的线程
if index<count:
t=myThread(index,threadIndex)
threads.append([threadIndex,t])
if start: #是否立即开启线程
t.start() #替换结束的线程时需要立即开启
return threads
#
index=0 #任务序号
threads=[] #线程列表,格式 [ 线程号 , 线程 ]
isStart=True #用于区分开始时创建的线程
while count >index:
threadIndex=0
while threadIndex<=(threadCount - len(threading.enumerate())) and count >index:
threads= addThread(threads,threadIndex,index)
index+=1
threadIndex+=1
if isStart==True: #任务开始时创建规定数量的线程(创建所有线程后再统一开启)
for t in threads:
t[1].start()
isStart=False #不再创建新的线程
while len(threads)>0: #自制阻塞,同时替换完成的线程
for t in threads:
if t[1].isFinish==True:#确定线程任务结束
threads= addThread(threads,t[0],index,1) #替换线程任务后立即开启线程
index+=1
print("【结束所有任务】")
if __name__ == '__main__':
main()
需要注意一点,在进行tkinter GUT 数据交互时,tkinter 界面时还是不能直接作用在小部件上,需要通过成员变量或合局变量传递信息,上面的 myThread 类可以增加传递数据的成员变量。