网络编程Day5-全局解释器锁GIL

GIL (Global Interpreter Lock)

Python代码执行由 Python 虚拟机 (又名解释器主循环) 进行控制。Python 在设计时是这样考虑的,在主循环中同时只能有一个控制线程在执行。对 Python 虚拟机的访问由全局解释器(GIL) 控制,这个锁用于确保当有多个线程时保证同一时刻只能有一个线程在运行

GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念(这把GIL锁加在解释器层面的),而CPython是大部分环境下默认的Python执行环境(GIL并不是Python的特性,Python完全可以不依赖于GIL)

GIL遵循的原则:“一个线程运行 Python ,而其他 N 个睡眠或者等待 I/O.”(即保证同一时刻只有一个线程对共享资源进行存取)。



GIL的影响

这个例子中,两个线程分别对一个全局变量total进行加和减1000000次,但是结果并不是0!而是每次运行结果都不相同。

造成这个结果的原因是GIL的释放,python中有两种多任务处理:

  1. 协同式多任务处理:一个线程无论何时开始睡眠或等待网络 I/O,就会释放GIL锁(协程)
  2. 抢占式多任务处理:如果一个线程不间断地在 Python 3 运行15 毫秒,那么它便会放弃 GIL,而其他线程可以运行(线程)

在单核CPU下没什么不一样(也有可能有性能损失),但是在多核CPU下问题就大了,不同核心上的线程同一时刻也只能执行一个,所以不能够利用多核CPU的优势,反而在不同核间切换时会造成资源浪费,反而比单核CPU更慢。


上面的 +- 操作就是触发了抢占式多任务处理机制,导致了这么一种情况:当一个函数运行着计算密集型程序时,由于GIL的原因,只会在一个单CPU上面运行。 两个函数对于共享变量 total 进行的修改,没有同步变量,导致数据不安全现象。



解决方法

  • 可用 multiprocess 库替代 Thread 库,即使用多进程而不是多线程,每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。但这样的话也会带来很多其他问题,比如进程间数据通讯和同步的困难,但是,多进程的开销也会增大。
  • 多进程+协程(多用于解决IO密集型情景)
  • 用其他解析器。像JPython这样的解析器由于实现语言的特性,他们不需要GIL的帮助。用了Java/C#用于解析器实现。



总结

  • 在IO密集型型操作下,多线程还是可以的。比如在网络通信,time.sleep()延时的时候或者是 IO 操作时,有空余出来的时间,我们就可以对其他线程进行处理。

  • 在CPU密集型/计算密集型操作下(没有空余时间),多线程性能反而不如单线程,此时只能用多进程(从而打破GIL对多核的限制)

其实龟叔对这个GIL是有其考虑之处的,虽然python 有了这个缺陷,但是python社区的大佬们都在为了这个GIL 奋斗,相信不久的将来,这个缺陷会被解决。(对比python2.7,如今的python3.7已经达到了运行速度更加快的进展)