GIL 全局解释器锁
GIL介绍
python程序本质就是一堆字符串,所以运行一个python程序时,必须要开启一个解释器。但是在一个python程序中解释器只有一个,所有代码都要交给它来解释执行,当有多个线程都要执行代码时就会产生线程安全问题。
如python会帮我们进行内存管理,管理也是要写一堆代码,也需要开启一个线程(GC线程)来执行。那就是说就算程序没有自己开启线程,放入解释器执行时也会有多个线程,造成线程安全问题
GIL本质就是一把互斥锁,将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。所以为解释器加GIL锁,这样在Cpython解释器中,同一个进程下开启的多线程,同一时刻也只能有一个线程执行,无法利用多核优势
GIL对多线程的影响与使用
因此只能尽可能避免GIL锁影响程序的效率
1.使用多进程能够实现并行,从而更好的利用多核CPU
2.对任务进行分类
- 计算密集型:基本上没有IO操作,大部分时间都在计算,例如人脸识别、图像处理。由于多线程不能并行,应该使用多进程将任务分给不同CPU核心
- IO密集型:计算任务非常少,大部分时间都在等待IO操作。由于网络IO速度对比CPU处理速度非常慢,多线程不会造成太大的影响,另外如有大量客户端连接服务,进程根本开不起来,只能用多线程
GIL与自定义锁的区别
GIL锁住的是解释器级别的数据
自定义锁锁的是解释器以外的共享资源,例如:硬盘上的文件应该自己加锁处理
from threading import Thread,Lock
import time
lock = Lock()
a = 0
def task():
global a
lock.acquire()
temp = a
time.sleep(0.01)
a = temp + 1
lock.release()
t1 = Thread(target=task)
t2 = Thread(target=task)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
过程分析:
1.线程1获得CPU执行权,并获取GIL锁执行代码 ,得到a的值为0后进入睡眠,释放CPU并释放GIL,不释放lock
2.线程2获得CPU执行权,并获取GIL锁,尝试获取lock失败,无法执行,释放CPU并释放GIL
3.线程1睡醒后获得CPU执行权,并获取GIL继续执行代码 ,将temp的值0+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为1
4.线程2获得CPU执行权,获取GIL锁,尝试获取lock成功,执行代码,得到a的值为1后进入睡眠,释放CPU并释放GIL,不释放lock
5.线程2睡醒后获得CPU执行权,获取GIL继续执行代码 ,将temp的值1+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为2