Scala的演员是否与Go的协同程序相似?

时间:2014-03-24 22:01:03

标签: scala go

如果我想移植一个使用Goroutines的Go库,那么Scala会是一个不错的选择,因为它的inbox / akka框架在本质上与协同程序类似吗?

5 个答案:

答案 0 :(得分:124)

不,他们不是。 Goroutines基于通信顺序过程理论,正如Tony Hoare在1978年所指出的那样。这个想法是可以有两个进程或线程彼此独立地行动但共享一个“通道”,一个进程/线程放置数据进入和其他进程/线程消耗。你会发现最突出的实现是Go的频道和Clojure的core.async,但此时它们仅限于当前的运行时,即使在同一个物理盒上的两个运行时之间也无法分发。

CSP演变为包含一个静态的,正式的过程代数,用于证明代码中存在死锁。这是一个非常好的功能,但Goroutines和core.async目前都不支持它。如果他们这样做,那么在运行代码之前知道是否存在死锁是非常好的。但是,CSP不能以有意义的方式支持容错,因此您作为开发人员必须弄清楚如何处理通道两端可能发生的故障,并且这样的逻辑最终会遍布整个应用程序。

演员,由Carl Hewitt于1973年指定,涉及拥有自己邮箱的实体。它们本质上是异步的,并且具有跨越运行时和机器的位置透明性 - 如果你有一个actor的引用(Akka)或PID(Erlang),你可以给它发消息。这也是一些人在基于Actor的实现中发现错误的地方,因为你必须引用另一个actor才能向它发送消息,从而直接耦合发送者和接收者。在CSP模型中,信道是共享的,并且可以由多个生产者和消费者共享。根据我的经验,这不是一个问题。我喜欢代理引用的想法,这意味着我的代码没有散布着如何发送消息的实现细节 - 我只发送一个,并且无论actor位于何处,它都会收到它。如果该节点发生故障并且演员在其他地方转世,那么理论上它对我来说是透明的。

演员有另一个非常好的功能 - 容错。通过按照Erlang中设计的OTP规范将actor组织到监督层次结构中,您可以在应用程序中构建失败域。就像值类/ DTO /您想要调用它们一样,您可以模拟失败,如何处理它以及层次结构的级别。这非常强大,因为您在CSP中几乎没有失败处理能力。

Actors也是一种并发范例,其中actor可以在其中具有可变状态,并且保证不会对状态进行多线程访问,除非构建基于actor的系统的开发人员意外地引入它,例如通过注册Actor作为回调的监听器,或通过Futures在actor中异步。

无耻的插件 - 我正在与Akka团队负责人Roland Kuhn一起写一本新书,称为Reactive Design Patterns,我们将讨论所有这些以及更多内容。绿色线程,CSP,事件循环,Iteratees,Reactive Extensions,Actors,Futures / Promises等。期待在下个月初看到Manning的MEAP。

祝你好运!

答案 1 :(得分:51)

这里有两个问题:

  • Scala是移植goroutines的好选择吗?

这是一个简单的问题,因为Scala是一种通用语言,并不比你可以选择的其他许多语言更好或更好。" port goroutines"。

当然有很多意见关于为什么Scala更好或更差作为一种语言(例如here是我的),但这些只是意见,并且不要让他们阻止你。 由于Scala是通用的,它几乎是"归结为:你可以用语言X做的一切,你可以在Scala中做。如果听起来太广泛了...... continuations in Java怎么样:)

  • Scala演员是否与goroutines相似?

唯一的相似之处(除了挑剔)是它们都与并发和消息传递有关。但这就是相似性结束的地方。

由于Jamie的回答很好地概述了Scala演员,我将更多地关注Goroutines / core.async,但是会有一些演员模型介绍。

演员可帮助您实现“无忧分发”#34;


无忧无虑" piece通常与以下字词相关联:fault toleranceresiliencyavailability等。

没有详细说明演员的工作方式,只需两个简单的术语演员:

  • 地区性:每位演员都有一个地址/参考,其他演员可以用来发送消息给
  • 行为:消息到达演员时应用/调用的函数

思考"谈论过程"其中每个进程都有一个引用和一个在消息到达时被调用的函数。

当然还有更多内容(例如,查看Erlang OTPakka docs),但上述两个是一个良好的开端。

演员对它感兴趣的地方是......实施。目前,两个大的是Erlang OTP和Scala AKKA。虽然它们都旨在解决同样的问题,但存在一些差异。让我们来看看几个人:

  • 我故意不使用"参考透明度"," idempotence"等等。除了引起混淆之外,它们没有任何好处,所以让我们这样做谈谈不变性[a can't change that概念]。 Erlang作为一种语言是固执己见的,它倾向于强烈的不变性,而在Scala中,很容易让演员在收到消息时改变/改变他们的状态。不建议这样做,但Scala中的可变性就在你面前,人们使用它。

  • Joe Armstrong谈到的另一个有趣的观点是,Scala / AKKA受到JVM的限制,而JVM实际上并未真正设计用于"正在分发"记住,虽然Erlang VM是。它涉及许多事情,例如:进程隔离,每个进程与整个VM垃圾收集,类加载,进程调度等。

上述观点并不是说一个人比另一个好,但是它表明作为一个概念的演员模型的纯度取决于它的实现。

现在来到goroutines ..

Goroutines有助于顺序推理并发


正如其他已经提到的答案,goroutines扎根于Communicating Sequential Processes,这是一种用于描述并发系统中交互模式的正式语言",根据定义,它几乎可以表示任何意义:)

我将基于core.async提供示例,因为我比Goroutines更了解它的内部结构。但core.async是在Goroutines / CSP模型之后构建的,因此在概念上不应有太多差异。

core.async / Goroutine中的主要并发原语是channel。想想channel作为"在岩石上的队列"。此频道用于"传递"消息。任何想要参与游戏的过程"创建或获取对channel的引用,并向/从中发送/接收(例如发送/接收)消息。

免费24小时停车

在频道上完成的大多数工作通常发生在" Goroutine"或" go block"," 接受其身体并检查任何频道操作。它会将身体变成状态机。在达到任何阻止操作后,状态机将被停放在'并将释放实际的控制线程。此方法类似于C#async中使用的方法。当阻塞操作完成时,代码将被恢复(在线程池线程上,或JS VM中的唯一线程)" (source)。

用视觉传达更容易。以下是阻塞IO执行的内容:

blocking IO

你可以看到线程大多花时间等待工作。这是同样的工作,但通过" Goroutine" /" go block"的方法:

core.async

这里有2个线程完成所有工作,4个线程在阻塞方法中完成,同时花费相同的时间。

以上描述中的踢球者是:"线程停放"当他们没有工作,这意味着,他们的状态得到了卸载"到一个状态机,实际的实时JVM线程可以自由地做其他工作(source以获得很好的视觉效果)

note :在core.async中,通道可以在" go block" s之外使用,它将由JVM线程支持停车能力:例如如果它阻止,它会阻止真正的线程。

Go Channel的力量

&#34> Goroutines" /"去块"是可以在通道上执行的操作。例如,可以创建timeout channel,它将在X毫秒内关闭。或选择/ alt!功能,当与许多频道一起使用时,就像"你准备好了吗?#34;不同渠道的轮询机制。将其视为非阻塞IO中的套接字选择器。以下是一起使用timeout channelalt!的示例:

(defn race [q]
  (searching [:.yahoo :.google :.bing])
  (let [t (timeout timeout-ms)
        start (now)]
    (go
      (alt! 
        (GET (str "/yahoo?q=" q))  ([v] (winner :.yahoo v (took start)))
        (GET (str "/bing?q=" q))   ([v] (winner :.bing v (took start)))
        (GET (str "/google?q=" q)) ([v] (winner :.google v (took start)))
        t                          ([v] (show-timeout timeout-ms))))))

此代码段取自wracer,它向所有三个人发送相同的请求:Yahoo,Bing和Google,并返回最快的结果,超时(如果在给定时间内没有返回,则返回超时消息)。 Clojure可能不是您的第一语言,但您对这种并发性顺序的外观和感觉的看法不一致。

您还可以从/向多个频道合并/扇入/扇出数据,映射/缩小/过滤/ ...频道数据等。频道也是一等公民:您可以将频道传递给频道..

Go UI Go!

因为core.async"去块"有这种能力,以及#34; park"执行状态,并具有非常顺序的外观和感觉"在处理并发时,JavaScript怎么样? JavaScript中没有并发性,因为只有一个线程,对吧?并发模仿的方式是通过1024个回调。

但它不一定是这样的。来自wracer的上述示例实际上是用ClojureScript编写的,它编译为JavaScript。是的,它可以在具有许多线程和/或浏览器的服务器上运行:代码可以保持不变。

Goroutines与core.async

同样,一些实施差异[还有更多]强调理论概念在实践中并非一对一的事实:

  • 在Go中,键入了一个频道,在core.async中,它不是:例如在core.async中,您可以将任何类型的消息放在同一个频道上。
  • 在Go中,您可以在频道上添加可变内容。不推荐,但你可以。在core.async中,通过Clojure设计,所有数据结构都是不可变的,因此通道内的数据对于它的健康感觉更安全。

那么判决是什么?


我希望上面阐述了演员模型和CSP之间的差异。

不要引起火焰战争,但要给你另一个观点,让我们说Rich Hickey:

" 我对演员仍然不热心。他们仍然将生产者与消费者联系起来。是的,人们可以模仿或实现某些类型的队列与演员(尤其是人们经常这样做),但由于任何一个演员机制已经整合了一个队列,所以很明显队列更原始。应该注意的是,Clojure的并发使用状态的机制仍然可行,并且通道面向系统的流程方面。"(source

然而,在实践中,Whatsapp基于Erlang OTP,它似乎卖得很好。

另一个有趣的引用来自Rob Pike:

" 缓冲发送未向发件人确认,可以任意长。缓冲通道和goroutine非常接近演员模型。

演员模型和Go之间的真正区别在于频道是一等公民。同样重要的是:它们是间接的,如文件描述符而不是文件名,允许在actor模型中不易表达的并发风格。还有一些情况正好相反;我没有做出价值判断。理论上,模型是等价的。"(source

答案 2 :(得分:8)

将我的一些评论转移到答案中。这太长了:D(不要带走杰米和托利斯的帖子;他们都是非常有用的答案。)

你可以做与Akka中使用goroutine完全相同的事情,这是不对的。 Go通道通常用作同步点。您不能直接在Akka中重现。在Akka中,后同步处理必须移动到一个单独的处理程序中(&#34;散布&#34;在Jamie的单词中:D)。我说设计模式不同。您可以使用chan启动goroutine,执行一些操作,然后<-等待它完成后再继续操作。 Akka与ask的形式不太强大,但ask并不是真正的Akka方式IMO。

也会键入Chans,而邮箱则不会。这对IMO来说是一个大问题,对于基于Scala的系统来说这非常令人震惊。我理解become很难用类型化消息来实现,但也许这表明become不像Scala那样。关于阿卡,我可以这么说。它经常感觉就像在Scala上运行一样。 Goroutines是Go存在的关键原因。

不要误解我的意思;我很喜欢演员模特,我一般都喜欢Akka,觉得上班很愉快。我也常喜欢Go(我发现Scala很漂亮,而我觉得Go只是有用;但它非常有用)。

但容错实际上是Akka IMO的重点。你碰巧得到了并发性。并发是goroutines的核心。容错中的容错是一个单独的事情,委托给deferrecover,可以用来实现相当多的容错。 Akka的容错更加正式且功能丰富,但它也可能有点复杂。

所有人都表示,尽管有一些相似之处,但Akka并不是Go的超集,而且他们在功能上有很大差异。 Akka和Go在如何鼓励你处理问题方面有很大的不同,而且在一方面容易的事情是尴尬的,不切实际的,或者至少在另一方面是非惯用的。这是任何系统中的关键区别。

所以把它带回到你的实际问题:我强烈建议在将它带到Scala或Akka之前重新考虑Go接口(这也是IMO的完全不同的东西)。确保按照目标环境的方式进行操作。复杂的Go库的直接端口可能不适合任何一种环境。

答案 3 :(得分:6)

这些都是非常彻底的答案。但是以一种简单的方式来看待它,这是我的观点。 Goroutines是Actors的简单抽象。演员只是Goroutines的一个更具体的用例。

你可以通过创建一个频道旁边的Goroutine来使用Goroutines实现Actors。通过决定该通道被Goroutine“拥有”,你说只有那个Goroutine会从中消耗掉它。你的Goroutine只是在该频道上运行一个收件箱消息匹配循环。然后,您可以简单地将频道作为“演员”(Goroutine)的“地址”传递。

但是由于Goroutines是一种抽象,比演员更通用的设计,Goroutines可以用于比Actors更多的任务和设计。

但是,由于Actors是一个更具体的案例,因此,像Erlang这样的演员的实现可以更好地优化它们(在收件箱循环中进行轨道递归),并且可以更容易地提供其他内置功能(多进程和机器演员)。

答案 4 :(得分:1)

我们可以说在参与者模型中,可寻址实体是参与者,即消息的接收者。而在Go渠道中,可寻址实体是渠道,即消息在其中流动的管道。

在“转到”频道中,您向该频道发送消息,并且可以收听任意数量的收件人,其中一个收件人将收到消息。

在Actor中,只有您发送消息给其actor-ref的actor会收到消息。