PyPy 计划去掉 GIL

http://morepypy.blogspot.com/2011/06/global-interpreter-lock-or-how-to-kill.html
强烈推荐去原文,底下的讨论都很有价值
翻译:马少兵/刘晓佳

在 EuroPython 会议上,Armin Rigo,也就是我本人,已经提过,PyPy 计划删除已经臭名昭著的全局解释器锁,那个妨碍 CPython 多线程并发能力的东东。

Jython 很久以前已经去除了 GIL。它非常小心地给所有 mutable 内置类型加锁,从而达到目的;由于依赖的底层 Java 平台可以较有效的完成实现,这种情况下效率比 CPython 里通过类似的机制达到 GIL-less 要快。另外,“非常小心地”,我的意思是非常非常小心地;例如‘dict1.update(dict2)’,需要同时锁住dict1和dict2,但一个粗糙的实现,也许会导致另一个线程执行‘dict1.update(dict2)’时进入死锁。

PyPy,CPython 以及 IronPython 都依赖 GIL。现在我们正在考虑一种不同于 Jython 这种加锁的机制,而是用 Software Transactional Memory 来去掉 GIL。STM 是计算机科学的一个最新发展,给出了一种不同于锁机制的很好的解决方案。下面通过一个简短的例子说明

假设你想通过pop方法从list1中得到值,并且利用append将该值附加给list2,函数如下:

def f(list1, list2):
    x = list1.pop()
    list2.append(x)

f 并不是线程安全的(甚至在有 GIL 的情况下也是如此)。假设你在thread1中调用f(l1,l2),在thread2中调用f(l2,l1)。你期望线程之间互相之间是没有影响(x将从一个list移动到另外一个,然后返回),但最后结果也许是两个列表的数据发生了交换,这个取决于并发时候的时间顺序。

通过一个全局锁可以防止该错误:

def f(list1, list2):
    global_lock.acquire()
    x = list1.pop()
    list2.append(x)
    global_lock.release()

另外一种更好的方法就是将相关的list进行加锁:

def f(list1, list2):
    acquire_all_locks(list1.lock, list2.lock)
    x = list1.pop()
    list2.append(x)
    release_all_locks(list1.lock, list2.lock)

第二种解决方案就是 Jython 的模型,而第一种是以Cpython为模型的。的确我们可以看出,在 CPython解释器中,我们获得GIL,执行一个字节码(或者执行100个字节码)以后,然后释放该锁;然后重复上述过程

第三种方案是STM:

def f(list1, list2):
    while True:
        t = transaction()
        x = list1.pop(t)
        list2.append(t, x)
        if t.commit():
            break

在这种解决方案下,我们首先需要创建一个事务对象,然后在所有的读写list操作中使用它。上面我们介绍了几种不同的模型,下面我们主要对STM做详细的介绍。在一个事务处理期间,我们不需要改变全局内存,相反使用一个局部线程的事务对象,存储它到一个可以进行读写操作的地方。在这个事务结束前,我们利用“commit”方法提交它。提交的过程可能由于其他的提交此时也在进行而失败,在这种情况下,事务将中止,且必须重新开始。

正如前两个方案分别是CPython和Jython模型, STM方案看起来像是PyPy未来将采用的模型。在这种模型中,解释器会启动一项事务,操作字节码,然后关闭事务,反复这样下去。这与在CPython使用GIL非常类似。特别是,这意味着它给程序员做出和GIL模式一样的保证。唯一的区别是,只要代码不会互相干扰,就能够真正并行运行多个线程。(当然如果你需要的不仅仅是GIL而是多线程程序中的锁,即使有了STM你还是需要获得锁。如果你希望用类似 STM 的机制来避免使用锁的话,也许可以用额外的内建模块将 STM 暴露给Python程序,不过那就是另一个问题了)

为什么不把这种思想应用到CPython中呢?因为这样做的话,我们可能会改变一切。你也许已经注意到了,在上面的例子中,我们不再调用list1.pop()函数,改为调用list1.pop(t);这种方式说明为了实现事务性的完成任务,所有方法的实现都需要改变。这意味着为了避免改变全局内存,必须用 transcation object 记录改变。如果我们的解释器像CPython一样用C写,那么我们需要显式的在每个地方写。相反,如果我们如PyPy那样用高层语言写,我们可以加入这种行为,把它作为解释规则的一部分,然后在需要的地方自动应用。而且,还可以提供一个解释期选项:你可以得到GIL版本“pypy”,或者STM版本。STM 由于增加了额外记录可能会变得更慢。(有多慢?我无法提供线索,但可以猜一猜,也许会慢2-5倍。如果你有足够的CPU内核,只要扩展性够出色,那不是问题。)

最后需要注意:由于STM的研究是最近的事(始于2003年),现在并不确定它的几个变种之中哪个更好。就我目前所了解的,“A Comprehensive Strategy for Contention Management in Software Transactional Memory”[PDF] 看起来是最美好的一种可能,在各种情况下都还不错。

那么什么时候能实现呢?我无法确定。现在仍旧处在创意阶段,不过我想会实现的。需要花多久来写呢?也没有线索,不过我们认为得几个月而不是几天。我打算在9月1日欧洲之星当前的赞助到期后,我就开始全职做这件事情。目前我们打算通过crowdfunding 的模式融资支持我的全职工作。可能很快就有一篇相关的博客发表,看起来这会是 crowdfunding 的一个很好的案例——我相信至少有数千人愿意花10欧元来去掉GIL。下面就等着我们的消息吧

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。