文章目录
1. GIL全局解释器
1.1 python执行一个文件的过程
-
引子
知乎,百度一些大V说python并发效率不行,不能多线程,并行不行等等。我们下面就仔细研究一下。
-
python执行文件的过程
你在终端输入python py文件的路径回车就执行了python文件,他在内存中的整个过程是什么样的?
在内存中开启一个进程空间,将python解释器与py文件同时加载到内存中。将py文件当做实参,将python解释器当做函数,执行函数的过程,最终返回一个结果。
理论上来说,我们一个py文件可以开启多线程,这些线程都可以进入CPython解释器然后并行的执行任务。
**但是!**这个只是理论来说的。python的单个进程的多个线程应用不了多核。CPython源码的程序员给进入解释器的线程加了一把锁,就是我们常说的互斥锁。
一个进程下的多个线程不能并行,但是可以并发。
1.2 为什么加这把锁GIL锁?
历史原因:
- 当时那个年代是单核时代,而且CPU价格非常昂贵,python期初作为一种脚本语言,面临的需求单核解决足以。
- 如果不加这把锁,同一时刻进入CPython解释器线程数量不定,我们要保证CPython解释器的数据资源安全,就需要在源码内部需要主动加入大量的互斥锁保证数据安全性,这样非常麻烦并且对于CPython源码的开发速度势必减慢。
1.3 为什么不去掉这把锁?
CPython解释器内部的管理以及业务逻辑全部是围绕单线程实现的,并且从龟叔创建CPython到现在,CPython源码已经更新迭代马上到4版本了,源码内容体量庞大,如果要去掉,这个工程无异于重新构建python,太难。
CPython解释器是官方推荐的解释器,处理速度快,功能强大。
JPython就是编译成Java识别的字节码,没有GIL锁。
pypy属于动态编译型,规则和漏洞很多,现在还在测试阶段(未来可能会成为主流)没有GIL锁。
只有CPython解释器有GIL锁,其他类型的解释器以及其他语言都没有。
这把锁不是python语言的缺陷,而是CPython解释器的缺陷。
1.4 这把锁带来的影响以及如何解决?
这把锁带来的影响是单进程下多线程不能利用多核并行,只能利用单核并发。怎么优化?
- 我们可以用多进程的并行替代,只不过可能是开启进程有些开销大,但是效率差不多。
- 我们也可以采用C的模块或者嵌入C语言去处理这种单进程的多线程的并行的问题。
1.5 这把锁真的是影响开发效率么?
我们多线程或者多进程的处理任务,此时这个任务分为两种。
IO密集型:我们以后从事的开发面对业务,基本上都是IO密集型,对于IO密集型单个进程下的多线程并发解决就可以了。
IO密集型:操作系统可以操控着CPU遇到IO就将CPU强型的切换执行另一个任务,而这个任务遇到IO阻塞了,马上又会切换,所以IO密集型利用单个进程的多线程并发是最好的解决方式(后面还会有协程也非常好用)。
计算密集型:多个任务都是纯计算都没有IO阻塞,那么此时应该利用多进程并行的处理任务。
小结:
- GIL全局解释器锁只存在于CPython解释器中,他是给进入解释器的线程上锁带来的影响:
- 优点:便于CPython解释器的内部资源管理,保证了CPython解释器的数据安全。
- 缺点:单个进程的多线程不能利用多核。
- GIL全局解释器锁并不是让CPython不能利用多核,多进程是可以利用多核的,况且IO密集型的任务,单个进程的多线程并发处理足以。
- IO密集型:单个进程的多线程并发处理。
- 计算密集型:多个进程并行处理。
2. GIL锁与普通的互斥锁的区别
- 两把锁保护对象不同
- GIL锁保护的是解释器以及各种库的数据安全。而普通的互斥锁保护的是我们自己写的程序的数据安全。
- 两把锁的性质相同
- GIL锁实际上就是互斥锁,只是保护的对象不同。
3. CPython解释器并发效率验证
3.1 IO密集型的效率验证
多进程并行:
from multiprocessing import Process
from threading import Thread
import time
import random
def task():
res = 0
for i in range(5):
time.sleep(1)
res += 1
if __name__ == '__main__':
start_time = time.time()
l1 = []
for i in range(4):
p = Process(target=task,)
p.start()
l1.append(p)
for i in l1:
i.join()
print(f'执行时间是{time.time()-start_time}') # 5.1...
多线程并发:
from multiprocessing import Process
from threading import Thread
import time
import random
def task():
res = 0
for i in range(5):
time.sleep(1)
res += 1
if __name__ == '__main__':
start_time = time.time()
l1 = []
for i in range(4):
p = Thread(target=task,)
p.start()
l1.append(p)
for i in l1:
i.join()
print(f'执行时间是{time.time()-start_time}') # 5.0...
-
小结:
IO密集型来说,单进程下的多线程的并发与多进程的并行效率差不多。
多进程的并行:
from multiprocessing import Process from threading import Thread import time def task1(): res = 0 for i in range(1000000): res += 1 def task2(): res = 0 for i in range(1000000): res -= 1 def task3(): res = 2 for i in range(1000000): res *= 2 def task4(): res = 10 for i in range(1000000): res /= 2 if __name__ == '__main__': start_time = time.time() l1 = [] p1 = Process(target=task1,) p2 = Process(target=task2,) p3 = Process(target=task3,) p4 = Process(target=task4,) p1.start() p2.start() p3.start() p4.start() p1.join() p2.join() p3.join() p4.join() print(f'执行时间是{time.time()-start_time}') # 15.89...
多线程的并发:
from multiprocessing import Process from threading import Thread import time def task1(): res = 0 for i in range(1000000): res += 1 def task2(): res = 0 for i in range(1000000): res -= 1 def task3(): res = 2 for i in range(1000000): res *= 2 def task4(): res = 10 for i in range(1000000): res /= 2 if __name__ == '__main__': start_time = time.time() l1 = [] p1 = Thread(target=task1,) p2 = Thread(target=task2,) p3 = Thread(target=task3,) p4 = Thread(target=task4,) p1.start() p2.start() p3.start() p4.start() p1.join() p2.join() p3.join() p4.join() print(f'执行时间是{time.time()-start_time}') # 16.12...