GIL: global interpreter lock (cpython)
GIL控制的字节码的执行,锁控制的是Python代码
- 什么是字节码,怎么查看字节码?
#通过dis模块查看函数add的字节码
import dis
def add(a):
a = a+1
return a
print(dis.dis(add))
#运行结果
6 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 STORE_FAST 0 (a)
7 8 LOAD_FAST 0 (a)
10 RETURN_VALUE
2.GIL作用
GIL使得同一时刻只有一个线程在一个cpu上执行字节码。保证字节码的执行是线程安全的
有了GIL是不是就是多线程安全的,不需要考虑线程间同步呢?
答案肯定不是的,因为GIL会在适当的时候释放的。举例说明
total=0
def add():
#1. dosomething1
#2. io操作
#3. dosomething3
global total
for i in range(1000000):
total +=1
def desc():
global total
for i in range(1000000):
total -=1
import threading
thread1=threading.Thread(target=add)
thread2=threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(total)
#运行结果:每次结果都不一样
不是说有GIL吗? 为什么还会出现这种现象呢?
首先,了解GIL的知识点:
- 对于有I/O操作的多线程,每次遇到I/O操作便会进行GIL锁的释放
- 如果是纯计算的程序,没有I/O操作,解释器会根据sys.setcheckinterval的设置来自动进行线程间的切换,默认情况下每隔100个时钟(python的内部时钟,对应于解释器执行的指令)就会释放GIL锁从而轮换到其他线程的执行
以简单实例说明
# a是全部共享变量
def add1(a):
a += 1
def desc1(a):
a -= 1
import dis
print(dis.dis(add1))
print(dis.dis(desc1))
#运行结果
51 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 INPLACE_ADD
6 STORE_FAST 0 (a)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
55 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 INPLACE_SUBTRACT
6 STORE_FAST 0 (a)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
# 使用伪代码来说明,考虑一种极端情况来解释
add1
"""
1. load a # a=0, 切换线程到desc1 ①
2. load 1 # 1 ③
3. + # 1 ⑤
4. 赋值给a # a=1 ⑦
"""
desc1
"""
1. load a # a=0, 切换为add1 ②
2. load 1 # 1 ④
3. - # -1 ⑥
4. 赋值给a # a=-1 ⑧
"""
经过add1和desc1操作之后,a要不就是1或者-1.而不是结果0
总结:因为线程同步解决的是代码的线程安全性问题,而GIL解决的只是字节码的线程安全,概念不一样