全局解释器锁GIL

解释一下对GIL的理解?java

  GIL 又叫全局解释器锁,首先说一点,Python语言与GIL全局解释器锁没有关系,仅仅是由于历史缘由,在cpython解释器中还存在GIL难以移除。GIL是功能与性能权衡后的产物,它有着存在的合理性,也有着难以移除的历史客观因素。python

 

为何存在GIL?编程

  在早期的开发过程当中,由于物理因素限制,从最开始的单核CPU发展为多核CPU, 想要充分发挥多核CPU的性能须要利用到多线程编程,Python中一样引入了多线程编程,而多线程编程引入带来的问题是:线程之间数据的一致性和状态同步的问题。 而想解决这些问题最好的方法就是加一把锁,因此就有了GIL全局解释器锁这样一把大锁。多线程

  随着愈来愈多的代码库开发者接受GIL,然后逐渐大量依赖于这一特性进行开发,到最后发现GIL对多核CPU多线程编程的效率是低效影响的时候,想要移除这一特性,却发现已经很难了。并发

 

谈谈GIL的做用?性能

  第一个做用: 当前线程必须须要先获取GIL,才能进入CPU执行代码。GIL的存在保障了在同一时刻只能有一个线程获取GIL,执行代码。线程

  第二个做用:当遇到IO阻塞时,执行线程会释放GIL,给其余线程获取锁执行代码的机会。设计

  

  问题: 若是是CPU密集型,一直占有CPU,而没有遇到IO阻塞,是否是其余线程就没有机会执行?接口

  其实也并非这样,在解释器中会进行周期性的代码检测和执行代码调度,在Python2中使用的是计数器的方式释放GIL,就是当计数达到必定阀值,当前执行线程就会释放GIL给其余线程执行的机会。但会出现的问题就是: 当前执行线程刚释放了GIL可能又会当即再获取GIL进行执行。 在Python3中使用的是计时器的方式释放GIL,就是当前执行线程执行时间达到必定阀值就会释放GIL,给其余线程执行的机会。这样避免了当前执行线程刚释放GIL又当即获取的状况,同时在线程中增长了线程优先级,高优先级的线程能够迫使执行的线程释放GIL,进行执行。队列

 

谈谈GIL的设计缺陷和影响?

  在早期的开发过程当中,为了让各个线程可以平均的利用CPU的执行时间,python中采用的是计数的方式切换执行代码,就是当计数(执行的线程代码数)达到必定的阀值,执行线程就会释放GIL锁,给其余线程执行的机会。这一模式在单核CPU中没有问题,由于不管是其余哪一个线程被唤醒,都可以成功的获取GIL进入CPU执行代码。而在多核CPU中则会有问题,当唤醒其它核心上的线程时候,大多数状况下老是当前主线程刚刚释放GIL,又会当即再次获取GIL进行执行,而其余被唤醒的线程只能白白等待浪费CPU的执行时间,等到执行时间结束,会进入到待唤醒待调度状态,再次被唤醒,再次等待,如此恶性循环着。

  GIL的影响有: GIL无疑是一把全局排他锁,它的存在保证了再同一时刻只能有一个线程获取GIL进行执行代码,因此就没法让多核CPU多线程实现并行,而想充分发挥多核CPU的最大性能就是实现多任务并行。  下面解释一下什么是并发和并行。

  并发: 当任务数大于CPU核心数时,总有一些任务是没有在执行的,只不过是由于CPU的切换速度很快,让人感受像是多任务同时在一块儿执行。

  并行: 当任务数小于或者等于CPU核心数时,每一个任务都有一个对应的核来处理执行,是真正意义上的多任务同时一块儿执行。

 

如何避免GIL的影响?

  方法一: 更换python解释器,好比jpython,用java开发的python解释器。 但由于众多的库都是创建在GIL这一特性下开发的,因此更换解释器不少库用不了,不划算。

  方法二: 使用多进程代替多线程。 multiprocession库的开发很大程度上就是为了弥补threading库由于GIL特性低效的缺陷,它完整的复制了一份threading里面的API接口便于迁移管理。 惟一的不一样就是它是多进程而不是多线程,每个进程都有本身的GIL锁,不会出现进程直接GIL锁的竞争。而多线程的时候则会出现释放GIL多个线程同时争抢锁的状况,这样会浪费CPU的性能资源。

  但多进程也不是万良解药,它的引入同时会增长实现进程间通讯和状态同步的难度,在多线程中对公共资源进行修改,只须要在线程中gloab 声明一下就能够了,多线程之间是共享全局变量的。而在多进程中,则须要使用一个Queue队列,经过put 或者get来传递数据,增长了开发的难度。多进程间是不共享全局变量的。