在理解GIL前 首先要理解下python的线程。
线程执行过程上图是线程的执行过程。可以看到 GIL确保一个时刻只有一个线程执行。在碰到阻塞 比如IO等待时释放GIL。
一开始
第一步当线程一遇到一个IO事件后,释放掉GIL
释放GIL线程二拿到锁,进行上下文切换
切换有时会出现两个线程都是可用的情况
都可用这时就通过系统的优先级队列来调度。从就绪队列中拿到优先级高的,然后进行运算。
enter image description hereGIL的影响
通过一个简单的countdown代码来测试
num=1000000
def threaded_at_once():
threads = [num//2 for i in range(20)]
results = pool.map(countdown, threads)
def normal():
for i in range(concurrent):
countdown(num)
testnum = 10
a = timeit(normal, number=testnum)
b = timeit(threaded_at_once, number=testnum)
print('Normal', a)
print('thread', b)
# py3.5.2
# Normal 6.854097206157869
# thread 6.9655090331886695
# py3.6
# Normal 9.051292152972469
# thread 8.782672920645473
# py2.7
# Normal 3.92715933198
# thread 12.6787057864
可以看到不同版本的python较大这个是由于内部线程切换的逻辑不同带来的。
整体来看gil对程序还是有影响的。使用了多线程并没有带来很显著的提升。
在cpython的解释器中有这样一个函数。
sys.setcheckinterval(interval)
设置解释器的“检查间隔”。此整数值确定解释器检查周期性事件(例如线程切换和信号处理程序)
的频率。默认值为 100,表示每100个Python虚拟指令执行一次检查。将其设置为较大的值可以提高
使用线程的程序的性能。将其设置为值 <= 0检查每个虚拟指令,最大化响应度以及开销。
3.2 版后已移除: 此函数不再有效果,因为线程切换和异步任务的内部逻辑已被重写。
请改用 setswitchinterval()。
对于CPU密集的任务 GIL才会有影响。
为什么cpython要使用gil?
gil的好处:
增加单线程处理的速度
易于集成的C库通常不是线程安全的
在io密集任务线程是更好的选择
使得C扩展更容易编写
为什么有时有了GIL还需要自己手动给一些程序加锁?
GIL锁的层面是python 字节码层面的
而自己实现同步时用的锁是python层面的,相当于是用户锁
执行的粒度不同
一个简单的加一操作有多个原子操作。gil只确保在一个原子操作上是安全的
dis.dis(lambda x:x+1)
0 LOAD_FAST 0 (x)
3 LOAD_CONST 1 (1)
6 BINARY_ADD
7 RETURN_VALUE
在遇到性能瓶颈时,将这一部分拿出来,用C扩展,可以获得很大的提升。
https://www.yunxcloud.cn/post/136
参考
http://www.dabeaz.com/python/GIL.pdf
http://www.dabeaz.com/python/UnderstandingGIL.pdf
https://softwareengineering.stackexchange.com/questions/186889/why-was-python-written-with-the-gil
网友评论