时间:2021-01-10 11:02:28 | 栏目:Python代码 | 点击:次
1、GIL简介
GIL的全称为Global Interpreter Lock,全局解释器锁。
1.1 GIL设计理念与限制
python的代码执行由python虚拟机(也叫解释器主循环,CPython版本)来控制,python在设计之初就考虑到在解释器的主循环中,同时只有一个线程在运行。即在任意时刻只有一个线程在解释器中运行。对python虚拟机访问的控制由全局解释锁GIL控制,正是这个锁来控制同一时刻只有一个线程能够运行。
在调用外部代码(如C、C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于期间没有python的字节码运行,所以不会做线程切换)。
在python中使用都是操作系统级别的线程,linux中使用的pthread,window使用的是其原生线程。
从上面的概述中可以直观的看出py在同一时刻只能跑一个线程,这样在跑多线程的情况下,只有当线程获取到全局解释器锁后才能运行,而全局解释器锁只有一个,因此即使在多核的情况下也只能发挥出单核的功能。
那么这样看起来py不给力啊,GIL直接导致CPython不能利用物理多核的性能加速运行。那么为什么会有这样的设计?考虑到Guido van Rossum 在创造python的时候,上世纪90年代,多核cpu完全属于不可想象的,现在由于硬件发展速度太快,程序编写就要考虑用尽cpu的全部性能,否则就要被淘汰,那么对于python同样也要如此。
上面主要说的是这种设计的劣势,下面再讨论它的优势。
GIL的设计简化了CPython的实现,使得对象模型,包括关键的内建类型如字典,都隐式可以并发访问。锁住全局解释器使得其比较容易的实现对多线程的支持,但也折损了多处理器主机的并行计算能力。
但是不论标准的,还是第三方的扩展模块,都被设计成在进行密集计算任务时释放GIL。另外还有在做IO操作时,GIL总是被释放。对所有面对内建的操作系统C代码的程序来说,GIL会在这个IO调用之前被释放,以允许其它的线程在等待这个IO的时候运行。如果是纯计算的程序,没有IO操作,解释器会每隔100次或每隔一定时间15ms去释放GIL。
这里可以理解为IO密集型的python比计算密集型的程序更能利用多线程环境带来的便利。
1.2 GIL对线程执行的影响
多线程环境中,python虚拟机按照以下方式执行:
上节说到python语言和程序一样要考虑用尽cpu的性能,下面在讨论py的应对方法。
python的应对方法很简单,在新的python3中依然有GIL,原因大概有下几点:
然后最重要的还在于以下几个方面,简单来说就是py不改,一样能实现我们的需求。
python中GIL使得同一个时刻只有一个线程在一个cpu上执行,无法将多个线程映射到多个cpu上执行,但GIL并不会一直占有,它会在适当的时候释放
import threading count = 0 def add(): global count for i in range(10**6): count += 1 def minus(): global count for i in range(10**6): count -= 1 thread1 = threading.Thread(target=add) thread2 = threading.Thread(target=minus) thread1.start() thread2.start() thread1.join() thread2.join() print(count)
分别运行三次的结果:
-59452
60868
-77007
可以看到count并不是一个固定值,说明GIL会在某个时刻释放,那么GIL具体在什么情况下释放呢:
1.执行的字节码行数到达一定阈值
2.通过时间片划分,到达一定时间阈值
3.在遇到IO操作时,主动释放