day9-继承式多线程
概述
前面通过实验,我们知道了线程的启动方式——直接调用,下面还有一种调用方式——通过类进行调用(继承式调用)
继承式多线程
import threading import time class MyThread(threading.Thread): #继承threading.Thread def __init__(self, n): # threading.Thread.__init__(self) super(MyThread,self).__init__() #重写父类构造函数 self.n = n #在类里面必须要run方法名字,不能使用别的 def run(self): # 重写run方法 print("running task:",self.n) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) #生成一个线程实例 t2 = MyThread(2) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 #运行输出 running task: 1 running task: 2 Process finished with exit code 0
解析:继承式多线程是先定义一个类,再继承threading.Thread。这就是使用类去启动线程。
我们之前只是启动了2个线程,如果我们想一次启动50个线程,那么如何启动呢?那么加入循环就可以了,接着我们来看看多线程
启动多线程
我们这里为了方便实验,就启动10个线程吧,暂时不启动那么多,并且计算一下时间。有人会说,为什么不启动1000个线程或者更多一点的线程。这边注意了:如果你的计算机是4核的,它能干的事情,就是4个任务。你启动的线程越多,就代表着要在多个线程之间进行上下文切换。相当于我有一本书,我还没看半页,因为cpu要确保每个人都能执行。也就是说我一本说我要确保60个人都能看到,那就相当于每个人执行的时间非常少。我把这本说拿过来,一下子又被第二个人,第三个人拿走了。所以就导致所有的人都慢了,所以说启动1000个线程,启动10000就没有意义了,导致机器越来越慢,所以要适当设置。下面我们就来看看一下子启动多个线程的例子
import threading,time def run(n): # 这边的run方法的名字是自行定义的,跟继承式多线程不一样,那个是强制的 print("task:", n) time.sleep(2) print("task done", n) start_time = time.time() # 开始时间 for i in range(5): # 一次性启动5个线程 t = threading.Thread(target=run, args=("t-%s" %i,)) t.start() print("--------all theads has been finished") print("cost:", time.time() - start_time) # 计算总耗时 #运行输出 task: t-0 task: t-1 task: t-2 task: t-3 task: t-4 --------all theads has been finished cost: 0.0008099079132080078 task done t-1 task done t-2 task done t-4 task done t-3 task done t-0 Process finished with exit code 0
可以看到,启动主程序(主线程)之后,没有等待其他子线程执行完毕,就直接继续往下执行了,这是为什么呢?我们中间明明隔了2s。
解析:一个程序至少有一个线程,那先往下走的,没有等的就是主线程,主线程启动了子线程之后,子线程就是独立的,跟主线程就没有关系了。主线程和它启动的子线程是并行关系,这就解释了为什么我的主线程启动子线程之后,没有等子线程执行完毕,而继续往下走了。所以我们计算不出来时间,因为程序已经不是串行的了。每次t.start()就启动一个新的子线程,子线程和主线程没有关系,相互是独立的(默认情况下主线程不会等待子线程执行完毕)。而且此程序本身就是一个线程,就是主线程,主线程启动了子线程。但是我就是想测试总共花了多长时间,怎么办?
尝试:使用join()方法
功能:去设置等待子线程的执行结果
说明:通过设置在主线程里去等待子线程的执行结果,子线程执行完def run()函数后,子线程就退出了,即代表执行结束了
import threading import time class MyThead(threading.Thread): def __init__(self,n): super(MyThead,self).__init__() self.n = n def run(self): "这个方法不能叫别的名字,只能叫run方法" print("runinit task",self.n) time.sleep(2) t1 = MyThead("t1") t2 = MyThead("t2") t1.start() t1.join() #等待t1线程的执行结果 t2.start() (隐含有个join()) #运行输出 running task t1 (隔2s) #等待t1的执行结果输出后再执行t2 running task t2 Process finished with exit code 0
解析:因为t2.start必须等待t1执行完毕后返回执行结果,t2才能执行,所以这个程序就变成串行的了,而t2后面没有join(),但是程序在运行结束退出之前,它要确保所有的线程都要执行完毕,所以最后默认就有一个join()。
产生的问题:我想要的效果明明就是线程依然是并行的,只不过是等所有的子线程执行完毕后,主线程再继续往下执行。
import threading, time class MyThead(threading.Thread): def __init__(self, n, sleep_time): # 增加时间参数 super(MyThead, self).__init__() self.n = n self.sleep_time = sleep_time def run(self): print("running task", self.n) time.sleep(self.sleep_time) # 每个线程可以传入不同的时间 print("task done,", self.n) t1 = MyThead("t1", 2) # 线程t1传入2秒 t2 = MyThead("t2", 4) # 线程t2传入4秒 t1.start() t2.start() t1.join() # 把t1.join()放在2个线程启动之后,表明等待t1的执行结果 print("main thread.....") #运行输出 running task t1 running task t2 task done, t1 main thread..... task done, t2 Process finished with exit code 0
解析:t1.join()在这里是等待t1的执行结果,然后主程序(主线程)继续往下走,打印出main thread.......,t2此时正在执行,因为t2需要等4秒,所以,最后打印出来的是t2的执行结果。同时,这里有2个线程t1(等待2s),t2(等待4s),我想要2个线程全部执行完所要花的时间,应该在4s左右,如果在t1.join()后面直接打印时间(算出结果),那么也就是2s(此时t2还没执行完毕,而上面的程序中,没有写t2.join()等待t2的执行结果,所以只是2s,而不是4s。如果我们要把2个线程执行完后统一算结果,应该怎么办?还得等t2的执行结果,在t1.join()后面加上t2.join()。
回到启动多线程,计算一下启动5个进程的时间,所以要join() 5次(要等所有的线程全部执行完毕)
import threading, time def run(n): print("task:", n) time.sleep(2) print("task done", n) start_time = time.time() t_objs = [] # 存放子线程实例 for i in range(5): t = threading.Thread(target=run, args=("t-%s" %i,)) t.start() t_objs.append(t) # 为了不阻塞后面线程的启动,不在这里join(),先把结果放到一个列表中 for t in t_objs: # 循环线程实例列表,等待所有线程执行完毕 t.join() print("--------all thread has been finished") print("cost:", time.time() - start_time) #运行输出 task: t-0 task: t-1 task: t-2 task: t-3 task: t-4 task done t-1 task done t-2 task done t-0 task done t-3 task done t-4 --------all thread has been finished cost: 2.0036489963531494 Process finished with exit code 0
解析:如果想要所有线程的执行结果的话,先创建一个临时列表,然后每循环一次把线程实例append到该列表中(为了不卡住下一个线程的启动)等到整个循环结束,再启动一个循环去获取结果。
注意:并不是第一个启动的线程就是主线程,主线程是程序本身,你是看不到的,程序本身也是一个线程。