GIl全局解释锁
GIL:全局解释器锁。当我们使用多线程的时候,每一个进程中只有一个GIL锁,那么这多个线程中谁拿到GIL锁,谁就可以使用cpu(ps:多个进程有多个GIl锁,但每个进程中只有一个GIL),所以当python用cpython作为解释器的时候,多线程就不是真正意义上的多线程,属于伪并发的多线程。
更多内容可以参考 知乎
我们都知道,比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。看起来很不可思议?但是这就是GIL搞的鬼。任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器…
问题1: 什么时候会释放Gil锁
- 遇到像 i/o操作这种 会有时间空闲情况 造成cpu闲置的情况会释放GIl
- 会有一个专门ticks进行计数 一旦ticks数值达到100这个时候释放GIl锁 线程之间开始竞争GIl锁(说明:ticks这个数值可以进行设置来延长或者缩减获得Gil锁的线程使用cpu的时间)
问题2: 互斥锁和Gil锁的关系
- Gil锁 :保证同一时刻只有一个线程能使用到cpu
- 互斥锁 : 多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱
- 首先假设只有一个进程,这个进程中有两个线程 Thread1,Thread2, 要修改共享的数据data, 并且有互斥锁:
执行以下步骤:
- 多线程运行,假设Thread1获得GIL可以使用cpu,这时Thread1获得 互斥锁lock,Thread1可以改date数据(但并没有开始修改数据);
- Thread1线程在修改date数据前发生了 i/o操作 或者 ticks计数满100((注意就是没有运行到修改data数据),这个时候 Thread1 让出了Gil,Gil锁可以被竞争);
- Thread1 和 Thread2 开始竞争Gil (注意:如果Thread1是因为i/o 阻塞 让出的Gil,Thread2必定拿到Gil,如果Thread1是因为ticks计数满100让出Gil这个时候Thread1 和 Thread2 公平竞争);
- 假设 Thread2正好获得了GIL, 运行代码去修改共享数据date,由于Thread1有互斥锁lock,所以Thread2无法更改共享数据date,这时Thread2让出Gil锁, GIL锁再次发生竞争;
- 假设Thread1又抢到GIL,由于其有互斥锁Lock所以其可以继续修改共享数据data,当Thread1修改完数据释放互斥锁lock,Thread2在获得GIL与lock后才可对data进行修改
以上描述了互斥锁和Gil锁的 一个关系。
多进程中的GIL
每个进程被fork或spawn时,其实都是开启了一个新的 python解释器进程,所以每个子进程都拥有一个独立的GIL锁。这样的话每个进程里面至少有一个住线程在跑,进程内的线程就实行GIL机制。这样就可以发挥多核优势。可以参考文档 python 3.7 multiprocessing
总结:
1. 线程锁是fine-grained(细粒度)的锁,程序员需要自行加/解锁来保证线程安全;
2. 全局解释锁是coarse-grained(粗粒度)的锁,语言层面本身维护着一个全局的锁机制用来保证线程安全;
3. 前一种方式比较典型的是 Java, Jython 等, 后一种方式比较典型的是 CPython (即Python)。