Reload Original PagePrint PageEmail Page

PyPy 计划去掉 GIL - PyPy

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] 看起来是最美好的一种可能,在各种情况下都还不错。

::...


免责声明:
当前网页内容, 由 大妈 ZoomQuiet 使用工具: ScrapBook :: Firefox Extension 人工从互联网中收集并分享;
内容版权归原作者所有;
本人对内容的有效性/合法性不承担任何强制性责任.
若有不妥, 欢迎评注提醒:

或是邮件反馈可也:
askdama[AT]googlegroups.com



自怼圈/年番新

DU21.4
关于 ~ DebugUself with DAMA ;-)


关注公众号, 持续获得相关各种嗯哼:
zoomquiet


粤ICP备18025058号-1
公安备案号: 44049002000656 ...::