七、GIL全局解释器锁
GIL全局解释器锁
"""
重点:
1.GIL不是Python的特点而是CPython解释器的特点
2.GIL是保证解释器级别的数据的安全
3.GIL会导致同一个进程下的多个线程的无法同时执行
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病:同一个进程下多个线程无法利用多核优势
"""
三个需要注意的点:
#1.线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
#2.join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高
一、案例:验证GIL存在
# 验证进程间是相互隔离的
import time
from threading import Thread, Lock
a = 10
mutex = Lock()
def func():
global a #局部修改全局
b = a
a = b - 1
print(b,end=' ')
if __name__ == '__main__':
t_list = []
for i in range(10):
p = Thread(target=func)
p.start()
t_list.append(p)
for i in t_list:
i.join()
print(a)
'''
输出结果:
10 9 8 7 6 5 4 3 2 1 0
如果GIL不存在,那么我们同时创建多个线程对变量a进行修改,因为不存在锁的约束,最终我们应该都是输出9,输出结果显然不是如此
GIL的存在,是为了保证解释器级别数据的安全,即该进程下的所有线程抢GIL是为了抢解释器的执行权限,此刻所有子线程都没有IO操作,所以抢到了GIL就执行完,从而形成一个完整的串行
'''
二、GIL与Lock的作用不同
'''
首先我们要明白:
锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
由此得出结论:
保护不同的数据就应该加不同的锁。
因此:
GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock
所有线程抢的是GIL锁,或者说所有线程抢的是执行权限
在抢到GIL锁后,还需要抢Lock锁,从而保证线程间数据的同步
案例:GIL与Lock
# 验证进程间是相互隔离的
import time
from threading import Thread, Lock
a = 10
mutex = Lock()
def func():
global a #局部修改全局
mutex.acquire()
b = a
time.sleep(0.1)
a = b - 1
mutex.release()
print(b,end=' ')
if __name__ == '__main__':
t_list = []
for i in range(10):
p = Thread(target=func)
p.start()
t_list.append(p)
for i in t_list:
i.join()
print(a)
'''
输出结果:
10 9 8 7 6 5 4 3 2 1 0
如果不加入Lock锁,那么在time.sleep(0.1)的时候,线程就会将GIL释放,以此类推,所有线程都会得到b=a=10的值,从而进行a=b-1的操作时,得到的值都只会是9,数据的同步就出现的问题
'''
1、GIL与Lock综合分析
分析:
#1.100个线程去抢GIL锁,即抢执行权限
#2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
#3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
#4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
2、互斥锁与join的区别
'''
需知join是等待子线程所有的代码执行完,相当于锁住了子线程的所有代码,而Lock只是锁住一部分操作共享数据的代码。
Lock是比join效率更高的方法

浙公网安备 33010602011771号