全局解释器锁GIL

解释一下对GIL的理解?

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

 

为什么存在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来传递数据,增加了开发的难度。多进程间是不共享全局变量的。

 

posted @ 2018-11-09 00:28 itlongfei 阅读(...) 评论(...) 编辑 收藏