day9-继承式多线程
一、前言
之前,我们只是简单的介绍了多线程演示,也通过时间设置看出来了,多线程和单线程的不同。现在我们进行更深入的了解,来聊一聊,另外一种多线程方式,继承式多线程,和一个多线程的等待。
二、继承式多线程
2.1、定义
说明:继承式多线程是自己自定义类,去继承theading.Tread这个类,通过启动,去执行run方法中的代码。
import threading import time class MyThead(threading.Thread): #继承threading.Thread "继承式多线程" def __init__(self,n): super(MyThead,self).__init__() self.n = n def run(self): #重写run方法 "这个方法不能叫别的名字,只能叫run方法" print("runinit task",self.n) time.sleep(2) t1 = MyThead("t1") #实例化 t2 = MyThead("t2") t1.start() #启动一个多线程 t2.start()
三、启动多个线程
我们之前只启动了一个2个线程,还是用那种古老的方式t1,t2。那我想一下子起10个或者100个线程,该如何起呐?这边我们可以启动线程的时候,把它加到循环里面去。并且来计算一下它的时间。
3.1、启动多个线程
说明:我们这边为了方便实验,就启动5个线程吧,暂时不启动那么多,并且计算一下时间。那有些同学说了,你为啥不启动1000个线程或者更多一点的线程。这边注意了:你的计算机是4核的,它能干的事情,就是4个任务。你启动的线程越多,就代表着要在这个很多线程之间进行上下文切换。相当于我有一本书,我只看了半页,因为cpu要确保每个人都能执行。也就是说我一本说我要确保你们每一位同学都能看到,那就相当于每个人执行的时间非常少。我把这本说拿过来,一下子又被第二个人,第三个人拿走了。所以就导致所有的人都慢了,所以说你的线程启动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-{0}".format(i),)) t.start() print("--------all thead has finished") print("cost:",time.time()-start_time) #计算总耗时 #输出 task: t-0 task: t-1 task: t-2 task: t-3 task: t-4 --------all thead has finished cost: 0.00096893310546875 task done t-1 task done t-2 task done t-0 task done t-4 task done t-3
大家有没有从上面的程序发现问题,就是我主线程没有等其他的子线程执行完毕,就直接往下执行了,这是为什么呢?而且这个计算的时间根本不是我们想要的时间,中间的2秒哪里去了?
答案:一个程序至少有一个线程,那先往下走的,没有等的就是主线程,主线程启动了子线程之后,子线程就是独立的,跟主线程就没有关系了。主线程和它启动的子线程是并行关系,这就解释了为什么我的主线程启动子线程之后,没有等子线程,而继续往下走了。所以我们计算不出来时间,因为程序已经不是串行的了。程序本身就是一个线程,就是主线程。但是我就是想测试总共花了多长时间,咋办呐?不要着急精彩继续
四、等待线程执行结果
4.1、join设置等待线程执行结果
说明:通过设置在主线程里去等待子线程的执行结果,有了这个执行结果就完了,我要拿所有子线程的执行结果,如果有子线程的执行结果,我们一切都好办了。
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线程的执行结果,相当于于其他语言里面的 t1.wait() t2.start()
注:
- t1.join() => 等待第一个线程的执行结果,这个结果在没有返回之前,程序是不往下走的。所以你这个这个程序什么吗?这个程序变成串行的了。
- t2.start() => 这个后面没有写 join() 这个方法,但是程序在退出之前,它肯定要确保线程都执行完毕,所以它就默认就有一个join。所以最后不写。
4.2、实现并发效果
说明:上面虽然有我想要的结果,却失去了并行的效果。我想要的是线程依然是并行效果,但是只不过,所有的线程统一之后再等主线程往下走。
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("runinit 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()放在线程启动之后 print("main thead.....") #执行结果 runinit task t1 runinit task t2 task done, t1 main thead..... task done, t2
注意了:t1.join() => 这边只等t1的结果,然后主线程继续往下走,因为t2需要等4秒,所以,最后打出来的是t2的执行结果。t1的结果到了,就立刻算结果。这边只计算了t1的结果,没有t2的结果。如果想算t2的结果,就 t2.join()。上面代码不理解如图:
4.3、计算多个线程的执行时间
说明:我们用上面的执行重新改进一下第二知识点里面的代码,来计算一下10个线程启动执行的时间。
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-{0}".format(i),)) t.start() t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表中 for t in t_objs: #循环线程实例列表,等待所有线程执行完毕 t.join() print("--------all thead has 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-2 task done t-1 task done t-0 task done t-4 task done t-3 --------all thead has finished cost: 2.003025770187378
哈哈,这样结果正是我们想要的。