14 python学习笔记-多线程threading
1、进程:
进程(英语:process),是指计算机中已运行的程序。你可以理解成一个任务就是一个进程,比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。进程是很多资源的集合。多进程多用于CPU密集型任务(大量的计算)。
2、线程
线程(英语:thread)是操作系统能够进行运算调度的最小单位,是进程里边具体干活的,它被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。比如Word,它可以同时进行打字、拼写检查、打印等子任务,这些进程内的这些“子任务”称为线程(Thread)。多线程多用于IO密集型任务(磁盘数据的读取和写入,网络的IO数据传输)。
注:python中的多线程并不是真正意义上的多线程,因为python解释器使用了GIL的全局解释锁
GIL全局解释器锁:不能利用多核CPU,只能运行在一个cpu上面,但是你在运行程序的时候,看起来好像还是在一起运行的,是因为操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。这个叫做上下文切换。
二、多线程threading
- Python通过两个标准库thread和threading提供对线程的支持。thread提供了低级别的、原始的线程以及一个简单的锁.Threading模块封装了一些常用的方法,初学者直接学这个模块就行了。
- Python中使用线程有两种方式:函数(函数式)或者用类(继承式、封装式)来包装线程对象
- threading.Thread里面几个参数介绍:
class Thread(_Verbose) __init__(self, group=None, target=None, name=None, args=(), kwargs=None, verbose=None) *group*:group参数必须为空,参数group是预留的,用于将来扩展; 参数args和kwargs分别表示调用target时的参数列表和关键字参数。 *target*: 参数target是一个可调用对象(也称为活动[activity]),在线程启动后执行 *name*: 参数name是线程的名字。默认值为“Thread-N“,N是一个数字。 *args*:传递给线程函数target的参数,他必须是个tuple类型. *kwargs*:kwargs表示关键字参数。字典类型 {}.
1、函数式多线程
下面是一个简单的函数式多线程:
1 import threading 2 3 #定义每个线程要运行的函数 4 def down_load(num): 5 print('等待下载完第%d个文件'%num) 6 7 def music(): 8 print('听着音乐') 9 10 def eat(food,drink): 11 print('吃着%s,喝着%s'%(food,drink)) 12 13 if __name__ == '__main__': 14 #创建线程组 15 threads=[] 16 #创建线程t1,t2,t3 17 # 1、函数不传参数 18 t1=threading.Thread(target=music) 19 threads.append(t1) 20 # 2、传kwargs参数 21 t2=threading.Thread(target=eat,kwargs={'food':'炸鸡','drink':'可乐'}) 22 threads.append(t2) 23 #3、带参数的用args传元组类型(参数最后多加一个逗号“,”要不然会报错) 24 t3 = threading.Thread(target=down_load,args=(1,)) 25 threads.append(t3) 26 27 #启动线程t3,t2,t1 28 for t in threads: 29 t.start()
=========执行结果==================
Thu Nov 21 23:56:36 2019听着音乐
Thu Nov 21 23:56:36 2019吃着炸鸡,喝着可乐
Thu Nov 21 23:56:36 2019等待下载完第1个文件
2、继承式多线程
下面是另一种启动多线程的方式,继承式
1.start()方法 开始线程活动。 对每一个线程对象来说它只能被调用一次,它安排对象在一个另外的单独线程中调用run()方法(而非当前所处线程)。 当该方法在同一个线程对象中被调用超过一次时,会引入RuntimeError(运行时错误)。 2.run()方法 代表了线程活动的方法。 你可以在子类中重写此方法。标准run()方法调用了传递给对象的构造函数的可调对象作为目标参数,如果有这样的参数的话,顺序和关键字参数分别从args和kargs取得
1 import threading,time
2
3
4 #1.先写一个执行函数,用来实现做某件事情,不同的人在做不同的事用两个参数people,food。
5 def cook(people,food):
6 print('%s%s正在做%s'%(time.ctime(),people,food))
7
8
9 #2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法
10 class MyThread(threading.Thread):#继承父类threading.Thread
11 def __init__(self,people,food,name):
12 '''重写threading.Thread初始化内容'''
13 threading.Thread.__init__(self)
14 self.threadName=name
15 self.people=people
16 self.food=food
17
18 def run(self): # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
19 '''重写run方法'''
20 print('开始线程:'+self.threadName)
21
22 cook(self.people,self.food) #执行任务
23 print('等待中\n')
24 print('结束线程'+self.name)
25
26 if __name__ == '__main__':
27 #创建新线程,并将线程添加到线程组
28 threads=[]
29 t1=MyThread('小米','红烧鱼','thread-1')
30 threads.append(t1)
31 t2=MyThread('小明','水煮牛肉','thread-2')
32 threads.append(t2)
33 t3=MyThread('小美','佛跳墙','thread-3')
34 threads.append(t3)
35
36 #启动线程
37 for t in threads:
38 t.start()
39
40 print(time.sleep(1))
41 print('退出主线程')
==============执行结果==============
开始线程:thread-1
Fri Nov 22 00:31:37 2019小米正在做红烧鱼
开始线程:thread-2
等待中
Fri Nov 22 00:31:37 2019小明正在做水煮牛肉
开始线程:thread-3
Fri Nov 22 00:31:37 2019小美正在做佛跳墙
等待中
结束线程Thread-3
退出主线程
结束线程Thread-1
等待中
结束线程Thread-2
从以上执行结果看,主线程已退出,子线程Thread-1和Thread-2还在运行,这就需要用到后面讲的等待线程和守护线程了
三、守护线程(setDaemon())
1、定义 :主线程结束了,子线程必须也跟着结束,这些子线程叫做守护线程
- 主线程中,创建了子线程thread1和thread2、thread3,并且在主线程中调用了thread.setDaemon(),这个的意思是,把主线程设置为守护线程,这时候,要是主线程执行结束了,就不管子线程是否完成,一并和主线程退出.(注意:必须在start()方法调用之前设置,如果不设置为守护线程,程序会被无限挂起。)
- 线程有一个布尔属性叫做daemon。表示线程是否是守护线程,默认取否。当程序中的线程全部是守护线程时,程序才会退出。只要还存在一个非守护线程,程序就不会退出。
- 主线程是非守护线程。
- setDaemon(True)此方法里面参数设置为True才会生效
1 import threading,time
2
3
4 #1.先写一个执行函数,用来实现做某件事情,不同的人在做不同的事用两个参数people,food。
5 def cook(people,food):
6 print('%s%s正在做%s'%(time.ctime(),people,food))
7
8
9 #2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法
10 class MyThread(threading.Thread):#继承父类threading.Thread
11 def __init__(self,people,food,name):
12 '''重写threading.Thread初始化内容'''
13 threading.Thread.__init__(self)
14 self.threadName=name
15 self.people=people
16 self.food=food
17
18 def run(self): # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
19 '''重写run方法'''
20 print('开始线程:'+self.threadName)
21
22 cook(self.people,self.food) #执行任务
23 print('等待中\n')
24 print('结束线程'+self.name)
25
26 if __name__ == '__main__':
27 #创建新线程,并将线程添加到线程组
28 threads=[]
29 t1=MyThread('小米','红烧鱼','thread-1')
30 threads.append(t1)
31 t2=MyThread('小明','水煮牛肉','thread-2')
32 threads.append(t2)
33 t3=MyThread('小美','佛跳墙','thread-3')
34 threads.append(t3)
35
36 #启动线程
37 for t in threads:
38 t.setDaemon(True) #守护线程在启动线程之前加上
39 t.start()
40
41 # time.sleep(0.5)
42 print('退出主线程')
43 ===============================================执行结果===================================================
44 =====================================主线程结束,未运行的子线程也结束不再运行====================================
45 开始线程:thread-1
46 Mon Nov 25 17:54:04 2019小米正在做红烧鱼
47 等待中
48
49 结束线程Thread-1
50 开始线程:thread-2
51 Mon Nov 25 17:54:04 2019小明正在做水煮牛肉
52 等待中
53
54 结束线程Thread-2
55 开始线程:thread-3
56 Mon Nov 25 17:54:04 2019小美正在做佛跳墙
57 等待中
58
59 退出主线程
可以看到,加上守护线程后,主线程结束,未运行的子线程也结束不再运行
四、线程等待join(timeout)
- 如果想让主线程等待子线程结束后再运行的话,就需要用到join(),此方法是在start之后(与setDaemon相反)
- join(timeout)此方法有个timeout参数,是线程超时时间设置。
1 import threading,time
2
3
4 #1.先写一个执行函数,用来实现做某件事情,不同的人在做不同的事用两个参数people,food。
5 def cook(people,food):
6 print('%s%s正在做%s'%(time.ctime(),people,food))
7
8
9 #2.使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法
10 class MyThread(threading.Thread):#继承父类threading.Thread
11 def __init__(self,people,food,name):
12 '''重写threading.Thread初始化内容'''
13 threading.Thread.__init__(self)
14 self.threadName=name
15 self.people=people
16 self.food=food
17
18 def run(self): # 把要执行的代码写到run函数里面 线程在创建后会直接运行run函数
19 '''重写run方法'''
20 print('开始线程:'+self.threadName)
21
22 cook(self.people,self.food) #执行任务
23 print('等待中\n')
24 print('结束线程'+self.name)
25
26 if __name__ == '__main__':
27 #创建新线程,并将线程添加到线程组
28 threads=[]
29 t1=MyThread('小米','红烧鱼','thread-1')
30 threads.append(t1)
31 t2=MyThread('小明','水煮牛肉','thread-2')
32 threads.append(t2)
33 t3=MyThread('小美','佛跳墙','thread-3')
34 threads.append(t3)
35
36 #启动线程
37 for t in threads:
38 # t.setDaemon(True) #守护线程在启动线程之前加上
39 t.start()
40 t.join() #等待线程,所有子线程结束,主线程才结束
41
42 # time.sleep(0.5)
43 print('退出主线程')
=============================执行结果==========================================
开始线程:thread-1
Mon Nov 25 18:00:10 2019小米正在做红烧鱼
等待中
结束线程Thread-1
开始线程:thread-2
Mon Nov 25 18:00:10 2019小明正在做水煮牛肉
等待中
结束线程Thread-2
开始线程:thread-3
Mon Nov 25 18:00:10 2019小美正在做佛跳墙
等待中
结束线程Thread-3
退出主线程
五、线程锁Lock
定义:线程锁就是,很多线程一起在操作一个数据的时候,可能会有问题,就要把这个数据加个锁,同一时间只能有一个线程操作这个数据。
1 #多个线程操作同一个数据的时候,就得加锁 2 import threading 3 4 num = 0 5 6 lock = threading.Lock() #申请一把锁 7 8 def add(): 9 global num 10 # lock.acquire()#加锁 11 # num+=1 12 # lock.release()#解锁 #死锁 13 with lock:#简写,用with也会帮你加锁,解锁 14 num+=1 15 16 for i in range(20): 17 t = threading.Thread(target=add,) 18 t.start() 19 #循环条件:当运行的线程数不等于1,效果与线程等待join的作用一致 20 while threading.activeCount()!=1: 21 pass 22 23 print('over',num)
运行结果:over 20
六、多线程爬虫下载图片实例
1 import requests,time,threading
2 from hashlib import md5
3 result_list = {}
4 def down_load_pic(url):
5 req = requests.get(url)
6 m = md5(url.encode())
7 file_name = m.hexdigest()+'.png'
8 with open(file_name ,'wb') as fw:
9 fw.write(req.content)
10 result_list[file_name] = threading.current_thread()
11
12 url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png',
13 'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png',
14 'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png',
15 'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']
16
17 #多线程运行时间
18 start_time = time.time()
19 for url in url_list:
20 t = threading.Thread(target=down_load_pic,args=(url,))
21 t.start()
22
23 while threading.activeCount()!=1:
24 pass
25 end_time = time.time()
26 print(end_time - start_time)
27
28 #下面是单线程的运行时间
29 # start_time = time.time()
30 # for url in url_list:
31 # down_load_pic(url)
32 # end_time = time.time()
33 # print(end_time - start_time)
七、线程池threadpool
1、内置的multiprocessing模块里面也有线程池,那么它和我们常常在代码里面看到的threadpool模块有什么区别呢?
- threadpool模块是一个很老的实现python线程池的模块,是第三方库;
- threadpool官方说它已经被废弃了,虽然它在Python2和Python3里面还能用。
- 官方建议用multiprocessing和Python3里面的asyncio替代它。
2、threadpool模块的使用介绍
- 引入threadpool模块
- 定义线程函数
- 创建线程 池threadpool.ThreadPool()
- 创建需要线程池处理的任务即threadpool.makeRequests()
- 将创建的多个任务put到线程池中,threadpool.putRequest
- 等到所有任务处理完毕theadpool.pool()
3、线程池使用实例
1 import threadpool
2 import requests,threading
3 from hashlib import md5
4 def down_load_pic(url):
5 print(threading.current_thread())
6 req = requests.get(url)
7 m = md5(url.encode())
8 with open( m.hexdigest()+'.png','wb') as fw:
9 fw.write(req.content)
10 url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png',
11 'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png',
12 'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png',
13 'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']
14 #实例化一个线程池,设置线程池中线程个数为20
15 pool = threadpool.ThreadPool(20)
16 #分配数据,将每个任务放到线程池中,等待线程池中线程各自读取任务,然后进行处理
17 reqs = threadpool.makeRequests(down_load_pic,url_list)
18 # for req in reqs:
19 # pool.putRequest(req)
20 [pool.putRequest(req) for req in reqs] #将创建的多个任务put到线程池中
21 print(threading.activeCount())
22 pool.wait() #等待
23 print('end')