随着 Ruby 1.9 增加了纤程 Fibers(协同程序),以及最近 Erlang 和 Actors 的流行,一组少为人之的概念进入了 Ruby 的编程世界。为了了解 Ruby 世界中的并发程序,我们采访了 Ruby 社区的老会员 MenTaLguY 。他长期致力于 Ruby 中的并发程序和线程机制,例如 fastthread 库,通过 1.8.x 的 MRI 改进了线程机制。最近他还涉猎了 Rubinius 。另外他还是 JRuby 小组的成员。
InfoQ: 请介绍一下你的 Ruby 版 Actors 库?
MenTaLguY: 实际上,我写了两个 Actors 库(已经发布)。一个是 Omnibus 并发库,另一个是 Rubinius 标准库中的一部分。两者都是 Ruby 实现的 Actors 模型的,也就是由 Erlang 普及的并发模型。并发程序, 从代码并行运行的意义上讲,并不难做到。麻烦出现在当不同的控制线程需要共享同一个资源或者通信路径时。如果你对此不采用一些简单的正规模型来进行结构化,基本上就不可能写出正确的或者最起码有意义的代码,尽管他们表面上看起来能“工作”。
“Actors”就是一个正规模型。一个 actor 由一个信箱和一个线程组成。它很灵活,actor 线程可以等待信箱中出现特定种类的消息,然后“执行”相应的动作,另外也可能再发消息发给别的 actor。通过这种自动而显式的消息交换方式,线程可以由一种相对容易理解的方式来通讯。
InfoQ: 它与 Ruby 的线程体系或者 Ruby 中新的纤程 / 协程有什么关系?
MenTaLguY: 我的 actor 库只是简单地把每个 Ruby 线程都关联一个信箱,这样每个线程都有了一个 actor。但这并不是在 Ruby 中使用 actor 的唯一方式。而纤程只是单线程中的协同调度任务,而且你也可以基于 actor 来实现,就像 Tony Arcieri 在他的 Revactor 库中所做的那样。由于纤程比完全线程更轻量级,而且你无需担心抢占问题,因此他的方法很有优势。然而有时你还是需要用完全线程(有时 Ruby 标准库会迫使你使用它)。
Tony 和我曾进行了多次有益的设计讨论;计划让 actor 的实现尽量有好的兼容性,并提供简单的对象协议,让每个 actor 实现都可以使用这个协议。从外部看, actors 同样是多态的——你最终都会把消息提交给一个信箱。基本上不用关心它是到底一个线程还是一个纤程,或者是什么运行在另一个 Ruby 虚拟机上的东西。原则上,actor-duck 甚至还可以由一些 Erlang 进程来支持 (例如通过 Scott Fleckenstein 的 Erlectricity).
InfoQ: 我看到你最近对 Rubinius 库的一些贡献,例如这个对付Actor 。Actors 是用在Rubinius 中吗?
MenTaLguY: 并不是它的一部分(这也是我把它从内核移到标准库中的原因之一)。我认为对它是需求还不到那个程度。
InfoQ: Actors 或者用它们实现的信箱有没有可能被用于 Evan 最近在 Rubinius 加入的 Multi-VM IPC 的消息传递中?
MenTaLguY: Actors 并没有用于实现 MVM IPC 机制,但我们想在幕后使用 MVM IPC 来允许不同 VM 中的 actors 互相通讯。
InfoQ: 现在 Rubinius 线程体系处于什么样的开发状态?都用到了什么——用户线程、内核线程,还是两者的 m:n 混合?
MenTaLguY: 我们在 VM 中使用了用户线程,但每个 VM 都运行在不同的内核线程中。现在,如果你想使用所有的 CPU,就要为每个 CPU 建立一个或两个 VM。Evan 想要最终在 VM 中使用 m:n 模式,但在 Ruby 中还有许多技术难关需要克服。甚至 Ruby 1.9 依然在原生线程上裹足不前,所以实际上他们还是用户线程。
可能只有基于支持原生线程运行时(比如 XRuby、JRuby、IronRuby)的 Ruby 实现才完全支持原生线程。如果 MVM 可以做得更轻量级或者多 CPU 之间的通讯变得足够复杂(世界正日益变得 NUMA 化),原生线程就不会变得那么重要了。
但是,有一种情况无法摆脱原生线程:使用那些不支持异步操作的设计糟糕的 IO API。在这种情况下,你需要使用多原生线程,而不是使用多核。有时你还不得不选择一个专门的进程来等待阻塞调用来结束这些,你的其它代码也随之结束。
但愿将来我们能少看到这种 API。Tony 的 Revactor 库带来了一线希望:他用 actor 来对付 IO,因此你的代码执行可以被 IO 事件驱动,而不是干等着阻塞调用的到来,或者承受控制的变换而变成了巨大的状态机。现在,Revactor 库还用在 MRI 1.9 上,但我们有望移植它,或者在 Ruby 中实现一个类似的库。
InfoQ: Rubinius 好像有了很丰富的并发概念和工具——线程、actor、多虚拟机+消息传递 IPC 等等。
MenTaLguY: 从历史上看,在这一点上,并发是一个非常重要的东西,我想是受了 Rubinius 的影响。
InfoQ: 我注意到其中的一个工具是通道——它在 Rubinius 中扮演什么角色?(我注意到快速调试器使用通道来通知调试器线程等)
MenTaLguY: 通道是 Rubinius 中的基本通讯方式;其它所有方式都基于它。基本的并发模型差不多就是异步的 pi 演算去掉同步和一些诸如非确定性选择之类的公用扩展(需要仲裁通道操作)。我提倡使用 pi 演算通道是由于它的简单性,换句话说是由于它的高效性和可维护性。
现在,pi 演算已经可以很好地直接用于“局部的”(VM 内的)地方,但还不太好用于实现分布式的地方,因为在 pi 演算中,通道两端都是可活动的。由于写操作是异步的,你可以随心所欲地写。但对通道的读操作是同步的。当一个通道遇到多个读操作时,这些读操作必须集中进行。当这些操作互相距离遥远时,就很不妙。
这就是我对 actor 如何应对大规模情况感兴趣的一个原因。Actor 信箱的异步计算特性与通道有点相像,除了只有(异步的)写操作一端是单独可活动的;读操作一端被紧密绑定在一个特定的本地代理上,且不需要考虑“远距离”协调的问题。
InfoQ: Ruby 1.9 增加了纤程和协程——它们是如何在 Rubinius 中实现的?
MenTaLguY: 我认为纤程在有了 Rubinius Tasks 之后实现起来并不麻烦;纤程和 Tasks 其实很相似。
InfoQ: 你对纤程和协程有什么见解和意见吗?你会使用他们吗——用来做什么呢?
MenTaLguY: 我认为他们可以很好地代替状态机,特别是协程令你更自由地使用库代码。不过,当状态机足够小或者在一些情况下,比如适合用 Ragel 之类地东西来产生它时,状态机仍是更好地选择。
InfoQ: 你现在拥有对 JRuby 的贡献权对吗?你对 JRuby 的兴趣在哪?或者说你致力于它的哪些方面?
MenTaLguY: 是的。我的主要兴趣是并发程序:Ruby 和原生线程的结合提出了一些有意思的挑战。因此我一直在修正并发程序的错误,并确定我们应该为并发程序提供怎样的保证。有可能的话我想最终把 Java 的并发程序的便利性引入 Ruby,希望通过移植的方式来进行(这也是 Omnibus Concurrency 库的一部分任务)。
InfoQ: 你还参与了其他什么项目吗?
MenTaLguY: 除了偶尔对 Shoes 打些补丁外,我从事一些未发布库的工作,其中大部分将会在准备好的时候发行和公布。这里可以讲讲我最近发行的一个库,尽管还没有正式公布:“case”gem 包。 它可以令 Ruby 的 case-match 运算支持数组、结构体和任意谓语的模式匹配。
<blockquote id="b39p">require 'rubygems'<br></br>require 'case'<br></br>Foo = Case::Struct.new :a, :b<br></br>def example(arg)<br></br> case arg<br></br> when Foo[:blarg, Object] # matches any Foo with .a == :blarg<br></br> # ...<br></br> when Foo[10, 20] # matches only a Foo with .a == 10 and .b == 20<br></br> # ...<br></br> when Foo # matches any Foo<br></br> # ...<br></br> when Case::Any[String, Array] # matches either a String or Array<br></br> # ...<br></br> # matches a three-element array with initial elements 1, 2:<br></br> when Case[1, 2, Object]<br></br> # ...<br></br> # matches any Integer > 10:<br></br> when Case::All[Integer, Case.guard { |n| n > 10 }]<br></br> # ...<br></br> end<br></br>end</blockquote>
Tony 和我在我们的 actor 库中使用 case-match 运算符(===)来选择特定种类的消息去等待。因此。这个 gem 包在这里很有用。
欲详细了解 MenTaLguY,请访问他的博客 http://moonbase.rydia.net/ 或者关注他所参与的项目。要详细了解 Actors,请阅读最近对Tony Arcieri 的采访,他是Revactor 的开发者。 Revactor 是一个为高性能网络应用开发的应用程序框架。它面向 Ruby 1.9,并使用了诸如纤程的并发特性。关于 Rubinius 的详情,请参见 InfoQ 的 Rubinius 专题内容。
查看英文原文: Ruby Concurrency, Actors, and Rubinius - Interview with MenTaLguY
评论