actor并发模型
随着Ruby 1.9添加了Fibers(Coroutines),以及最近Erlang和Actors的流行,一群鲜为人知的概念进入了Ruby编程世界。 为了大致了解Ruby空间中的并发性,我们与长期的Ruby社区成员MenTaLguY进行了交谈 。 他长期从事Ruby并发和线程的研究,例如使用fastthread库,该库通过1.8.x MRI改进了线程。 最近,他一直在Rubinius涉猎,并且还是JRuby团队的成员。
InfoQ :您的Ruby Actors库是关于什么的?
MenTaLguY :实际上,我已经编写了两个(发布的)Actor库。 一种是在Omnibus并发库中,另一种是Rubinius stdlib的一部分。 两者都是Actor模型(由Erlang推广的并发模型)的Ruby实现。 从并行运行代码的意义上讲,并发实际上并不难做到。 当不同的控制线程需要共享资源或进行其他通信时,会出现大问题。 如果您没有选择一些简单的正式模型来构建这种交流,那么即使表面上看起来“起作用”,也几乎不可能编写正确甚至是有意义的代码。
“演员”就是这样一种模式。 一个actor由一个邮箱和一个线程组成。 Actor线程可以自行决定等待特定类型的消息出现在邮箱中,然后对接收到的任何消息进行“操作”,有可能将消息发送给其他Actor。 这样,在自愿和显式交换消息的驱动下,线程可以以相对容易推理的方式进行通信。
InfoQ :它与Ruby的线程系统或Ruby的新Fibers / Coroutines有什么关系?
MenTaLguY :我的actor库仅将邮箱与每个Ruby线程相关联以组成每个actor,但这不是在Ruby中接触actor的唯一方法。 光纤只是在单个线程中的合作计划任务,您也可以基于Actor,就像Tony Arcieri在其Revactor库中所做的那样。 他的方法具有一些优势,因为光纤的重量比全线程轻,并且您无需担心抢占。 另一方面,有时您确实需要完整的线程(有时Ruby stdlib会将其强加给您)。
托尼和我一直在进行一些激烈而富有成效的设计讨论; 该计划是使我们的actor实现尽可能兼容,并提供一个简单的对象协议,任何其他actor实现也可以使用。 从外部看,演员们都可以使用相同的鸭子类型-毕竟,您始终只是将邮件提交到邮箱。 在大多数情况下,无论是线程还是光纤,还是在另一个Ruby VM中运行的将要拾取它们的东西都没有关系。 原则上,演员鸭甚至可以通过某个地方的Erlang过程来支持(例如,通过Scott Fleckenstein的Erlectricity)。
InfoQ :我看到了您最近对Rubinius存储库的一些提交,例如与Actors相关的提交 。 在Rubinius中使用演员吗?
MenTaLguY :不作为其实现的一部分(这是我将它们从核心转移到stdlib的原因之一)。 我认为在该级别上不需要它们。
InfoQ :是否有机会(或可以)在Evan最近添加到Rubinius的传递多VM IPC的消息中使用Actor或其邮箱实现?
MenTaLguY :不使用Actor来实现MVM IPC机制,但是我们想在幕后使用MVM IPC,以允许不同VM中的Actor相互通信。
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库在这方面提供了一线希望:它将演员带到IO上,以便您的代码执行可以由IO事件来驱动,而不是无用地等待阻塞调用或遭受控制反转并变成巨大的对象。显式状态机。 目前,它与MRI 1.9相关,但希望我们可以移植它或在其他Ruby实现中获得类似的东西。
InfoQ :Rubinius似乎带有大量的并发思想和工具-线程,参与者,multivm +消息传递IPC等。
MenTaLguY :在历史的这一点上,并发是一个特别重要的问题,我认为Rubinius反映了这一点。
InfoQ :我注意到的这些工具之一是渠道-它们在Rubinius中扮演什么角色? (我注意到快速调试器使用Channels通知调试器线程等)。
MenTaLguY :通道是Rubinius中的基本通信原语。 其他一切都在他们之上实现。 基本的并发模型或多或少是异步pi演算减去复制以及一些常见的扩展,例如非确定性选择(可能需要对通道操作进行集中仲裁)。 我之所以主张pi演算通道,是因为它们简单易用,通常可以转化为性能和可维护性。
现在,pi演算可以直接用于“本地”(VM内)事物,但对于实现分布式事物则不是那么好,因为在pi演算中,通道的两端都是可移动的。 当写入是异步的时,您可能会大失所望,但是在一个通道中进行读取是一个同步操作,如果一个通道进入了多个读取器的手中,则这些读取器必须在某个地方集合以进行读取。 如果他们彼此之间特别远,那不是一件好事!
这就是为什么我对演员感兴趣的原因之一。 Actor邮箱有点像异步演算中的通道,只不过(异步)写端是独立移动的。 读取端牢固地绑定到特定的本地代理,并且不需要特殊的“长距离”协调。
InfoQ :Ruby 1.9添加了Fibers / Coroutines-如何在Rubinius中实现它们?
MenTaLguY :我认为可以在Rubinius Tasks之上实现Fiber,而不会带来太多麻烦。 纤维和任务相当相似。
InfoQ :您对纤维/协程有什么看法/意见? 您会使用它们吗?用于什么用途?
MenTaLguY :我认为它们是显式状态机的不错选择,特别是因为协程为您提供了使用库代码时的更多自由。 但是,如果状态机足够小或在适合使用Ragel之类的东西为您生成状态机的情况下,状态机仍然可以更好。
InfoQ :您现在拥有JRuby的提交权限是否正确? 您对JRuby有什么兴趣,或者正在那里从事什么工作?
MenTaLguY :是的。 我的主要兴趣是并发性:Ruby和本机线程的组合提出了一些有趣的挑战。 因此,我一直在修复并发错误,并确定总体上应该提供什么样的并发保证。 我最终还是希望将Java并发功能正确地公开给Ruby,希望以一种可移植的方式进行(这部分是Omnibus并发库的任务)。
InfoQ :您还参与哪些其他项目?
MenTaLguY :除了偶尔为Shoe贡献补丁外,我还在幕后开发许多库,其中大部分将在准备就绪时宣布/发布。 但是,我可以指出我最近发布的一个,尽管它尚未正式发布:“ case” gem,它使您可以使用Ruby中的case-match运算符对数组,结构和任意谓词进行模式匹配。
require 'rubygems'
require 'case'
Foo = Case::Struct.new :a, :b
def example(arg)
case arg
when Foo[:blarg, Object] # matches any Foo with .a == :blarg
# ...
when Foo[10, 20] # matches only a Foo with .a == 10 and .b == 20
# ...
when Foo # matches any Foo
# ...
when Case::Any[String, Array] # matches either a String or Array
# ...
# matches a three-element array with initial elements 1, 2:
when Case[1, 2, Object]
# ...
# matches any Integer > 10:
when Case::All[Integer, Case.guard { |n| n > 10 }]
# ...
end
end
Tony和我都使用actor库中的case-match运算符(===)来选择要等待的消息种类,因此该gem在那里非常有用。
有关MenTaLguY的更多信息,请访问http://moonbase.rydia.net/上的博客,或观看他参与的项目之一。有关Actors的更多信息,请阅读最近对Revactor开发商Tony Arcieri的采访 。 Revactor是用于高性能网络应用程序的应用程序框架。 它针对Ruby 1.9,并利用诸如Fibres for Concurrency之类的功能。 有关Rubinius的更多信息,请参见InfoQ的Rubinius报道 。
actor并发模型