Python 3.2与更好的GIL
Python 3.2于已经正式发布。在Python 3.1发布之后,根据Guido的建议 (PEP 3003),Python的开发人员在2011年6月份之前,都不再对Python语言进行大的修改,而是将主要精力集中在修复bug,提高稳定性以及性能上。现在Python 3.2带着这样的目标到来了。Python 3.2带来的改进有很多,其中一个惹人注目的改动是重新实现的GIL (Global Interpreter Lock)。众所周知,Python(这里说的Python实际上是Python语言的参考实现CPython)的GIL是阻碍Python性能提升的一个屏障,因为有GIL的存在,即使Python提供了非常优雅简洁的threading库接口,你实际上还是无法享用多线程所带来的性能提升,因为本质上GIL只允许一个线程在Python进程下运行。也因此从GIL诞生的那天起,关于去掉GIL的争论就一直没停止过。对此,Guido以及Python的开发人员都有一个很明确的解释,那就是去掉GIL并不容易,或者说是不值当的。如果说在Python下使用多线程,因为GIL的存在而不起效用的话也就罢了。更令人不能接受的是,在某些情况下,使用多线程不光不能提高效率,反而会降低效率,尤其是在多CPU的机器上,效率反而比单CPU(将其余CPU禁止)要低很多。这样的结果无论如何是说不过去的。David Beazley曾经对此做过非常详细的分析,以及解释。原因大体如下:Python使用GIL来控制任何时候只能有一个线程运行,并且Python来控制线程何时释放GIL,而底层的OS来控制哪个线程获取GIL。Python在遇到两种情况下强制某个线程释放GIL,一是该线程要进行IO操作,二是该线程已经执行了某个数量的opcode。这样的机制就带来了
两个问题:
[*]使用固定数量的opcode做为单位来强制切换线程,必然是粗糙的,不准确的,因为有的代码可能执行的很快,而有的则更耗时。
[*]让OS来选择哪个线程执行,有时候是无法预测的(尤其在多CPU下)。比如有可能造成某个线程刚刚释放GIL,却马上又获取了GIL。
[*]
针对这些问题,Antoine Pitrou对GIL的实现进行了重写,他曾在Python的开发列表上详细阐述了重新实现的机制:
[*]使用固定的时间而不是固定数量的opcode来进行线程的强制切换。这样就解决了上述问题1,但使用固定的时间仍然是相当机械的,有可能在这个时间内,某个优先级更高的线程(比如一个IO等待操作正巧数据来了)需要运行,却只能等待。
[*]在线程释放GIL后,开始等待,直到某个其它线程获取GIL后,再开始去尝试获取GIL。这样可以避免此前获得GIL的线程,不会立即再次获取GIL,但仍然无法保证优先级高的线程获取GIL。
[*]
Pitrou对GIL的重新实现也就是现在3.2版本的GIL,解决了相当一部分问题。但仍然有部分极端情况未能解决,归根结底在于在GIL的释放由Python来控制,但GIL由哪个线程获取却仍然是底层的OS来决定的,而这仍然有可能带来性能的下降。为此Beazley专门向Python发了一个bug,附上了相关的测试脚本,并介绍了一种可能的解决办法。在这个bug的解决上,相关开发人员对此进行了详细的分析与测试,如果要解决这个问题,显然必须要把部分kernel的调度机制,挪到Python里,也就是必须对Python的线程区分优先级。Nir Aides为此专门将Kernel的Brainfuck调度算法,在Python里重新实现并做了相应的patch。但该patch并没有集成到3.2中,对此的解释是针对线程调度的修改最好慢慢来,这个问题需要实际的应用来证实确实需要修复。目前看来,也许在3.3中才能看到该修复。
当然即使该问题得到了修复,Python的多线程也看起来更加完善,但从根本上,仍然还在GIL上转圈圈。仍然会有刚了解Python的开发人员去质疑为什么不去掉GIL,尤其是在今天,多CPU已经成为标准的配置。但至少在很长的时间内,你听到的答复都将是,很困难,去不掉,你只能选择使用多进程,或者用C扩展的方式来绕过GIL。
这样的答复即使理由足够充分,也无法令人满意。毕竟在解决问题时,谁都希望能有更多选择。但事实上,Python在经过这么多年的发展后,对于GIL的依赖已经积重难返,悲观的说,GIL已不太可能从Python中去掉。除非某个有勇气的人,将Python重新实现。
页:
[1]