Rubinius 1.1 和 GIL 的未来

阅读数:709 2010 年 10 月 19 日

话题:RubyRuby on Rails语言 & 开发

Rubinius 1.1终于发布了(请访问在Rubinius 网站或者项目的GitHub 库取得下载,一睹为快,或使用用RVM安装)。Rubinius 1.1 的发布说明列出了一长串内容,介绍了改进和 bug 修复的信息,此外还提到了不少有用的新增功能。Rubinius 的调试器功能强、速度快,一直都让 Rubinius 引以为傲,在 1.1 版本增加了一些新的调试功能(摘自发布说明):

  • 新增调试器 API 和参考 CLI 调试器
  • 新增 heapdump 功能,用于内存调试
  • 新增用来检测不良扩展的代码。需要重新编译现有扩展
  • 为 VM 和 Ruby 崩溃新增了‘rbx report’命令及相关支持

要启用调试器,需要在命令行中加入(-Xdebug)开关,这样一来在启动时就可以直接引导你进入调试器命令行。另一种调用调试器的方式是通过调试器 API:“require 'debugger'”,然后在代码中调用“Debugger.start”就可以进入调试器。

目前,Rubinius 团队在忙于编写项目的文档,可以通过rbx docs命令来访问,打开浏览器浏览即可。

此外,性能问题也是关注的重点之一:

  • 增加自动对象实例变量打包(automatic object ivar packing)功能(提升内存利用率)
  • 在 JIT 中可以使用 block 内嵌(block inlining)功能
  • 实现新的 GIL 算法,防止线程饥饿死锁(starvation)

新的 GIL 算法采用了和Python 3.2 里重建的 GIL一样的方法,不过在 Rubinius 1.1 中,GIL仍是存在的

在 1.1 发布之前,InfoQ 连线了 Rubinius 的缔造者Evan Phoenix,询问Rubinius 项目 hydra 分支的相关信息,该分支旨在构建一个没有 GIL 的 VM。

InfoQ:关于在 Rubinius 里移除 GIL,你的计划是怎样的?

我所采用的策略如下:

1) 去掉 GIL 本身。这并不难,就是一个在几处被调用到的 C++ 类而已。

2) Rubinius 已经按照线程局域和共享的数据结构把代码组织起来了。我为共享的数据结构增加了互斥体,还给大多数方法上了锁。对于某些地方,我上锁上得有点过头了,不过就现在的情况来说没什么问题。为了简化加锁的操作,我采用了一些 C++ 技巧,比如使用了局域锁(scope lock),在处于相应作用域内部是,加解锁工作会自动完成。

3) 在刚引入 JIT 的时候,为了确保 GC 操作的安全,我不得不编写代码,让垃圾回收器停止(VM)系统内的所有线程。这么做是因为 JIT 是和执行中的 Ruby 代码并发执行的。既然 GIL 还在使用,这部分代码也被干净地重用着。

4) 一直以来我都使用 RubySpec 的线程测试规约(specs)来检测与修复线程相关的崩溃问题。这个方法很管用,因为这些测试规约重现了多种线程行为。多数修复都是围绕着确保线程能被妥善启动及清除来解决问题的。我使用互斥体和自旋锁(spinlock)的组合方案来保证同步。

5) 现在我已经开始运行整个 RubySpec 套件来检查更多的挂起和崩溃故障。

InfoQ:关于在 Rubinius 里移除 GIL,你的计划是怎样的?GIL 是会完全消失,还是在围绕着扩展或者其它系统的领域仍然被保留下来呢?

对于使用扩展的部分是有可能(保留下来的),原因很简单,因为扩展并没有被设计成可以并发运行的。C 扩展要访问 GC 对象,是通过句柄系统来完成的,这部分要实现线程安全不难。不过,因为 C 扩展使用了不一定线程安全的 C 库之类的东西,我们很有可能需要对此做好预防工作。

可能的做法,就是为所有扩展方法的执行增加一个锁,或者给每个扩展增加一个锁,然后让这个扩展里的所有扩展方法共享。这样就能实现对访问共享数据的代码的隔离,并能带来些许性能提升。

同时,每个扩展配一把锁的方案还能让扩展把自己的线程安全状态告诉系统,并让扩展忽略锁。很显然,这会变成对 C API 的一个扩展,但这确实是扩展作者们和我讨论过的事情。

Hydra 分支会被合并到未来的 Rubinius 分支上,这样一来 MRI 1.9.2 会变成仅存的带 GIL 的 Ruby VM;JRuby 和 IronRuby 都没有 GIL,而MacRuby 也在前一段时间放弃了 GIL

对 GIL 的功能和 Ruby 1.9.x 如何实现 GIL 的话题感兴趣的读者,可以看看 Elise Huard 关于 GIL(GVL)的文章。

在 Ruby(以及 Python)里难以并行运行线程的问题,导致了许多开发者把目光转向基于事件和非阻塞 I/O 的解决方案,此外,使用多个 OS 进程来实现负载分布也是常用的策略。近来不少围绕 Node.js 非阻塞特性的热烈讨论也与此相关。

不过,一旦不带 GIL 的 Ruby VM 和并行 Ruby 线程成为主流,到底会变成什么样子呢?这个趋势还可能逆转吗?

查看英文原文:Rubinius 1.1 - and the Future of the GIL