今天随意逛水木的精华区,看大家在讨论什么GIL,搜了一下发现python的多线程原来与我想象的大不同。看了几篇不错的文章,觉得挺不错的,大致对问题有了个了解,先把文章的地址贴出来,有兴趣去读这些文章的朋友就不必再听我这样的半拉子扯淡了:
Concurrency and Python
http://www.ddj.com/linux-open-source/206103078Python Threads and the Global Interpreter Lock
http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/本来是应该从并行、多线程、竞争、锁这些东西谈起,不过我想一般大家都该挺熟的,不熟悉的话也很容易找到资料,这里就偷懒略过了。
Python的多线程模型基本上是Java多线程模型的简化版,提供了线程基类、锁、信号量,事件等待组件,虽然也少了一些功能,比如线程不能被销毁、中止、暂停、恢复等待,基本上可以让程序员比较方便的进行多线程编程。在CPython解释器中,存在一个叫Global Interpreter Lock(GIL)的东西,直译就是全局解释器锁,它的作用在于让同一时刻只能有一个线程对于python对象进行操作。Python已经提供了各种机制让我们进行多线程同步,为什么又要整这个GIL呢?这是因为程序员控制的同步是对各个程序中可见的变量,而GIL同步的是解释器后台的不可见变量,比如为了进行垃圾回收而维护的引用计数。如果没有GIL,有可能出现由于线程切换导致的对同一个对象释放两次的情况。
因此,任何一个CPython线程如果要执行,就必须先获取这个GIL。后果?就是在CPython中,本质上几乎是没有线程并行的,不论你开多少个线程,同一时刻只有获取GIL的那个线程能够执行。为什么要说几乎呢,这是因为提供给python的C库中,还是有解决方案的,比如
... Py_BEGIN_ALLOW_THREADS sleep((int)secs); Py_END_ALLOW_THREADS ....
这段代码是sleep的代码,在执行sleep之前,通过一个宏来释放GIL,然后在睡眠结束执行其他代码前又获取GIL。其他一下操作,比如IO,也会有类似的操作,这样就使得对于IO密集型的程序,或者是使用C库进行计算的程序,还是可以在很大程度上避开GIL来取得线程并行的效果的。但对于纯python代码的程序,GIL恐怕还是躲不过去的。
还有一个问题,就是GIL怎么释放,我们看到在python/C API中提供了宏来进行释放,那么对于普通的python语句呢?解释器会在执行一百条python代码后强制释放GIL,这就使得其它线程得以执行。
最后需要说明的,就是这个GIL的问题是解释器相关的,而不是语言相关的。也就是说它只是对于python语言解释器的一种实现,并不是语言本身的特性。事实上,GIL就是解释器的一个非常粗粒度的锁,我们完全可以采用更细粒度的锁来增加并行性,而且Gindo就写过一个patch来取消GIL,不过好像最后的结果是细粒度锁导致了单线程程序的性能下降了两倍,所以最后还是决定优先保证单线程程序的性能,继续保留GIL。但是python的其他两个分支,Jython和IronPython,却都没有GIL的问题,从而可以实现线程的并行。为什么呢?你知道了麻烦给我说说。。。