深挖 GIL锁
重述 GIL 锁机制
我们知道GIL锁,它是用来保证线程安全的。
比如说,有一条代码 是 x = 10 的赋值语句,当你产生了 10 这个数值还没有进行赋值给x的时候,cpu发生了调度切换了,有可能切换到了 垃圾回收线程上,这个时候的垃圾回收线程就会发现有一个引用计数为 0 的内存空间,于是就把他给释放掉了,这就导致了这条赋值语句的失败。也就造成了线程不安全的情况。
所以为了解决这个问题,就有了GIL全局解释器锁。
大白话来讲GIL锁,就是一个进程开启了,这个进程一共有3个线程,这三个线程会去抢GIL锁,只有抢到了GIL锁,解释器进程才会去执行这个线程的代码,抢到了GIL锁以后,等待cpu调度切换到了这个进程,这个进程中的线程开始执行,cpu不停地在这三个线程切换,只有其中拿到了GIL锁的线程才能被执行,其他的两个线程,就算拿到了cpu的执行权,也没有被执行。
问题
现在就有个问题,那这个拿到了GIL锁的线程什么时候才会释放GIL锁?
答案
当遇到io等待的时候,就会释放这个GIL锁。
问题二
什么是io,其实不止是增删读写文件,赋值操作也是io,关于文件的操作是磁盘io,赋值操作是内存io,因为它涉及到内存的申请了。那么他不就是在赋值的时候就释放了GIL锁了吗?
答案
内存io不会造成io等待。所以不会释放GIL锁。
总述GIL锁
1.释放GIL锁的目标,是为了释放CPU,因为CPU在执行任务的角度上看是最珍贵的资源,没有之一,所以才让CPU去执行其它任务。
2.进程/线程自身只有分配给它的时间片到了,才会主动释放CPU,其它任何时候的CPU释放,都是被逼释放的,操作系统会强制收回。如果进程自身实现了调度器(比如GIL管理器,Go的goroutine管理器),进程自身也可以强制收回。还是那句话,收回CPU是为了不浪费CPU
3.如果进程想要和外界交互,比如读取read磁盘文件,程序自身是没有资格读的,只能通过read系统调用求操作系统。既然是求它做任务,当然要把CPU交还给操作系统,否则操作系统也没法执行任务。也就是说这个时候,CPU已经脱离了进程,而是被操作系统抓在手里。
3.发生IO等待,速度太慢太慢,所以操作系统为所有IO等待类的系统调用设置了IO等待事件。只要程序请求操作系统做某件事,操作系统肯定就知道这个请求会不会IO等待。如果会,操作系统直接就把CPU分给其它进程了,而不会再调度请求它的那个进程。
5.所以结论才是, IO等待导致重新调度其它进程/线程,CPU切换走。使用GIL的时候,解释器进程要调度执行其它线程分支,自然要先释放GIL锁,然后再锁被调度选中的线程。
也就是说,这种申请内存的操作,是在一个程序内部执行的,而不需要请求操作系统去做,自然就不算是io等待,不是io等待,也就不会发生cpu重新调度,所以解释器进程也就不会去调度执行其他线程分支,自然就不会释放gil,关键就是,只要不请求操作系统,操作系统就不知道,就不会收走cpu的执行权,所以赋值操作这种在程序内部完成的,是不会产生io等待的,就算是内存IO,哪怕是赋值100W个元素的列表,在申请内存的时候也不会主动释放CPU。
释放GIL锁的两种方式
1.主动释放:io操作的时候,就会释放,这属于主动释放
2.被动释放:占用GIL锁时间过长,如解释器不间断执行了1000字节码的指令或不间断运行了15毫秒。都会释放GIL锁。
注:要想执行一个线程,必须同时拿到GIL锁和cpu执行权。