Python学习中,我们完成多任务时,往往会使用多线程技术,那我们一定要知道GIL这个概念,GIL到底是做什么的?为什么要用GIL?它有哪些缺点?
什么是GIL
GIL即全局解释器锁(global interpreter lock),每个线程在执行前都需要先获取GIL,保证同一时刻只有一个线程可以执行,别的线程不能干扰当前线程的执行,只能在占有GIL锁的线程执行完之后再获取锁。
为什么要用GIL
为了更有效的利用多核处理器的性能,出现了多线程的编程方式,而随之带来的问题就是如何保证各个线程间数据一致性和状态同步。解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。CPython就设计了GIL这把全局锁来试图解决线程安全。但是它存在一些问题我们下面说明。
Note:CPython是特指用C语言实现的Python,即Python。Python还有一些其它的实现,比如Jython,是Java版的Python,还有PyPy,使用Python再实现的Python。各个解释器的详细信息读者可以自行百度。
GIL存在哪些问题
Python的线程,的确是封装的底层的操作系统的线程,也受到操作系统管理,但是,由于解释器的C语言实现部分在完全并行执行时并不能保证线程安全的。因此,解释器被GIL保护着,能确保任何时候都只能一个Python线程执行。
正因如此,GIL锁不能容忍一个线程一直占用资源,他会轮流执行python的其他线程,导致同一时刻只有一个线程使用CPU,也就是说多线程并不是真正意义上的同时执行,由于CPU执行速度够快从而达到了一种“伪多线程”的效果。
#测试多线程执行耗时--0.006826
from threading import Thread
def test_multi(n):
t1 = Thread(target=test, args=(n // 2,))
t2 = Thread(target=Decrement, args=(n // 2,))
t1.start()
t2.start()
t1.join()
t2.join()
test_multi(1000000)
#测试正常执行耗时--0.007235
def test(n):
while n > 0:
n -= 1
test(1000000)
我们发现,多线程与单线程的版本所运算的时间并没有想象中的提升很大。
而且,GIL也并不一定能够保证线程安全。虽然说Python有了GIL来锁住线程,但是并不意味着编写Python的程序的时候就不需要去注重线程安全了,因为有check_interval机制,还是会导致线程安全的问题。
check_interval这个机制在python2中和python3中是不一样的:
- python2: 执行时钟的100ticks(对应1000个bytecodes)去释放这个锁
- python3: 执行5ms,然后释放
而哪怕是一句简单的num+=n也是由多条bytecode组成,这些字节码执行到中间的时候可能会因为锁被剥夺而被打断,就会导致一条语句没有执行完,从而导致一致性被破坏。
所以也不能因为GIL就完全不注重race condition问题,使用Python多线程还是要加锁,threading模块的lock()方法,这样结果就不会出现差错了。实例化threading.Thread和继承threading.Thread这两种方式可以实现Python多线程,通过实例化的方式使用起来比较简单,使用继承的方式更加具有封装性。或者也可以使用封装好的一些工具类或者第三方模块。