GIL全局解释器锁
GIL:又叫全局解释器锁,每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行,目的是解决多线程同时竞争解释器资源而出现的线程安全问题。它并不是python语言的特性,仅仅是由于历史的原因在CPython解释器中难以移除,因为python语言运行环境大部分默认在CPython解释器中。
多线程下每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行。
由于GIL的存在,即使是多线程,事实上同一时刻只能保证一个线程在运行,既然这样多线程的运行效率不就和单线程一样了吗,那为什么还要使用多线程呢?
由于以前的电脑基本都是单核CPU,多线程和单线程几乎看不出差别,可是由于计算机的迅速发展,现在的电脑几乎都是多核CPU了,这时差别就出来了:
即使在多核CPU中,多线程同一时刻也只有一个线程在运行,这样不仅不能利用多核CPU的优势,反而由于每个线程在多个CPU上是交替执行的,导致在不同CPU上切换时造成资源的浪费,反而会更慢。
即原因是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源。
要去除GIL,主要要考虑四个主要技术点。
- 引用计数。引用计数在 Python 里是 GCC 的主要方式, C-API 里
Py_INCREF
和Py_DECREF
太好用了,所有C库都用。而这些操作都用到了GIL,只要引用计数活着一天,去掉 GIL 就不容易。 - 全局和静态变量。这个不用说。
- C扩展的并行和重入。
- 原子性。很多 Python 对象都保证原子性,比如基本类型 list、dict 啥的。
说到在这里要先介绍两个概念:计算密集型和IO密集型
计算密集型:要进行大量的数值计算,例如进行上亿的数字计算、计算圆周率、对视频进行高清解码等等。这种计算密集型任务虽然也可以用多任务完成,但是花费的主要时间在任务切换的时间。这类情况使用多进程实现多任务,可以充分利用多核cpu。
IO密集型:涉及到网络请求(time.sleep())、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。这类情况使用多线程实现多任务,速度要超过单线程的。
解决GIL问题的方案:
- 使用其他语言的解释器,如java的解释器jython
- 使用其它语言实现多线程,例如C,Java
- 使用多进程,是可以利用多核的CPU资源的优势