Python3 从零单排26_GIL全局解释器锁
先了解下CPU的简单运行原理:
它运行速度非常快,1s内可以运行成千上万次,一个核心可以把1s切分成成千上万个时间片段,这个核心确实同时只能运行一个任务;但是可以将多个任务交替执行,比如上一个时间片段内运行A任务,下个时间片段可以运行B任务,交替执行,因为时间片段很短,所以感觉就是同时在进行了。
再了解下单线程和多线程的区别:
先看下单进程,顾名思义,就是一条进程,类似于单向公路上只有一条车道,每次只能过一辆车,多进程则表示多个车道,可以同时过多辆车;那么单线程和多线程意义严格上来说不是进程这样的理解,因为进程内的线程同一时间点只能运行一个,不存在同时进行,CPU给我们的感觉的同时进行,只是它运行的非常快,交替执行多个线程差别可能是毫秒、微秒的区别,所以感觉不到差别,他们在同时进行。
接着就产生了单线程和多线程的疑惑:
既然上述说了,多线程并不是多个线程并发在同一时间点运行,而是CPU有计划的交替执行各个多线程,那多线程的优势在哪里?比如python里,从上运行到下调用多次同一个函数是个单线程,和把几次调用函数写成多线程,依据上述理论,这里的CPU运行时间并没有变快啊,因为多线程不能并发运行,也是一个个线程类交替执行完成啊,甚至多线程可能更慢,因为它还要花时间去管理交替执行任务上,不要怀疑,事实上就是如此,那么我们使用多线程的意义在哪里?
这里需要了解下GIL:
Python是解释型语言,那么它在运行的时候就需要解释器了,简单描述下GIL,即global interpreter lock,全局解释器锁,就是python在运行的时候会锁定解释器,就是说在运行的时候只能是一个线程,锁死了,切换不了;每个线程在运行之前都要申请GIL,那么就必须要等上一个线程释放这把锁你才可以申请到,然后执行代码,执行完后,你再交给下一个线程,让它去执行代码,过程如下:
设置GIL -> 切换到一个线程去执行 -> 运行 -> 把线程设置为睡眠状态 -> 解锁GIL
然后再次重复以上步骤。
IO密集型任务多线程比单线程要快太多:
貌似多线程比单线程还要耗CPU,而且运行速度又没变快,甚至更慢,这是相对于计算密集型任务(要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力)来说的,像这类计算密集型任务由于主要消耗CPU资源,python用多线程效率不会提高,甚至是会更慢,原理见上述GIL;
还有一种IO密集型任务,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),99%的时间都花在IO上,花在CPU上的时间很少;
原谅我不自觉的想到了爬虫,爬虫是典型的IO密集型任务,多线程的发送请求情况下:发送一个请求到收到服务器的响应数据取决于网络的快慢,那么发送一个请求之后就是等待服务器的响应了,期间会释放GIL锁,其他线程就可以申请到这把锁,进行发送请求了,重复上述操作直到最后一个请求,那么就等同于非常短的时间内,CPU发送了多个请求,接下来就是等待服务器的响应;那么如果是单线程呢?它发送一个请求后就在那里等着服务器响应,直到服务器有返回数据到客户端后,才会释放GIL锁,接着继续下一个请求,只能是一个个的排队,直到最后一个执行完,显而易见,这里的线程相当并发请求了,比单线程要快的多。
综上所述:
在处理计算密集型任务时,python的多线程劣与单线程,性能表现比单线程要差;
在处理IO密集型任务时,python的多线程优与单线程,性能表现比单线程要好太多;
我们来看下例子
1 # 计算密集型:用多进程 2 from multiprocessing import Process 3 from threading import Thread 4 import os,time 5 def work(): 6 res=0 7 for i in range(100000000): 8 res*=i 9 10 11 if __name__ == '__main__': 12 l=[] 13 # print(os.cpu_count()) #本机为8核 14 start=time.time() 15 for i in range(8): 16 # p=Process(target=work) #耗时8s多 17 p=Thread(target=work) #耗时37s多 18 l.append(p) 19 p.start() 20 for p in l: 21 p.join() 22 stop=time.time() 23 print('run time is %s' %(stop-start)) 24 25 26 # IO密集型:用多线程 27 from multiprocessing import Process 28 from threading import Thread 29 import threading 30 import os,time 31 32 def work(): 33 time.sleep(2) 34 35 if __name__ == '__main__': 36 l=[] 37 # print(os.cpu_count()) #本机为4核 38 start=time.time() 39 for i in range(400): 40 # p=Process(target=work) #耗时2.697多,大部分时间耗费在创建进程上 41 p=Thread(target=work) #耗时2.02多 42 l.append(p) 43 p.start() 44 for p in l: 45 p.join() 46 stop=time.time() 47 print('run time is %s' %(stop-start))