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

loy*_*low 73 scala go

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

jam*_*mie 130

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

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

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

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

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

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

祝好运!

  • +0.75 :)我认为"Nope"太强了.消息传递意义上有相似之处.细节和功能不同,但最终目标非常相似.两者都试图通过消息传递来解决并发编程问题. (4认同)
  • 这不是不合理的.它们都是异步的,尽管频道制作人会议频道消费者的连接点在时间上比演员的消防和遗忘模型更加耦合.它们都是基于消息的.在我看来,Actors非常适合容错和跨节点扩展,其中CSP是一种利用节点内多个线程的有效机制. (4认同)
  • @ user1361315,你可以用Akka做同样的事情并不完全正确.Go通道通常用作同步点.您不能直接在Akka中重现.在Akka中,后同步处理必须移动到一个单独的处理程序中(在jamie的话中用"散布"字样:D).我会说设计模式不同.你可以用一个chan启动goroutine,做一些东西,然后`<-`等待它完成然后继续前进.Akka与`ask`的形式不太强大,但`ask`并不是真正的Akka方式IMO.Chans也是键入的,而邮箱则不是. (3认同)

tol*_*ius 53

这里有两个问题:

  • Scala是一个很好的选择goroutines吗?

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

当然有很多关于为什么Scala 作为一种语言更好或更差的观点(例如这里是我的),但这些只是意见,不要让它们阻止你.由于Scala是通用的,它"几乎"归结为:您可以在语言X中执行的所有操作,您可以在Scala中执行.如果它听起来太广泛了..如何继续Java :)

  • Scala演员是否类似goroutines

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

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

演员帮助事情"无忧分发"


其中,"全程无忧"片通常与诸如有关:fault tolerance,resiliency,availability等.

没有详细说明演员的工作方式,演员可以用两个简单的术语来处理:

  • 位置:每个actor都有一个地址/引用,其他actor可以使用它来发送消息
  • 行为:当消息到达actor时应用/调用的函数

想想"谈话过程",其中每个进程都有一个引用和一个在消息到达时被调用的函数.

当然还有更多内容(例如,查看Erlang OTPakka docs),但上面两个是一个好的开始.

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

  • 我故意不使用诸如"引用透明度","幂等"等术语.除了引起混淆之外,它们没有好处,所以让我们谈谈不变性[ 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成"岩石上的队列".此频道用于"传递"消息.任何想要"参与游戏"的过程都会创建或获取对a的引用,并向其channel发送/接收(例如发送/接收)消息.

免费24小时停车

大多数在通道上完成的工作通常发生在" Goroutine "或" go block "中," 它会占用它的身体并检查它以进行任何通道操作.它会将身体变成状态机.在达到任何阻塞操作时,状态机将被"停放"并且实际的控制线程将被释放.这种方法类似于C#async中使用的方法.当阻塞操作完成时,代码将被恢复(在线程池线程上,或者JS VM中的唯一线程) "(来源).

用视觉传达更容易.这是阻塞IO执行的样子:

阻止IO

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

core.async

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

上面描述的踢球者是:"线程被停放 ",当它们没有工作时,这意味着,它们的状态被"卸载"到状态机,而实际的实时JVM线程可以自由地做其他工作(源代码很好的视觉效果) )

注意:在core.async中,channel 可以在 "go block"之外使用,它将由没有停放能力的JVM线程支持:例如,如果它阻塞,它会阻塞真正的线程.

Go频道的力量

"Goroutines"/"go blocks"中的另一个重要的事情是可以在频道上执行的操作.例如,可以创建超时通道,该通道将在X毫秒内关闭.或者选择/ alt!当与许多频道一起使用时,其功能类似于不同频道的"准备就绪"轮询机制.将其视为非阻塞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))))))
Run Code Online (Sandbox Code Playgroud)

此代码段取自wracer,在那里将其发送到所有三个相同的请求:雅虎,Bing和谷歌,以及最快的国家之一返回一个结果,超时(返回超时消息)如果没有一个给定的时间内返回.Clojure可能不是您的第一语言,但您不能不同意这种并发实现的顺序和感觉.

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

去UI去吧!

由于core.async"go blocks"具有"停放"执行状态的能力,并且在处理并发时具有非常顺序的"外观和感觉",JavaScript如何?JavaScript中没有并发性,因为只有一个线程,对吧?并发模仿的方式是通过1024次回调.

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

Goroutines与core.async

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

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

那么判决是什么?


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

不要引起火焰战争,而是给你另一个视角让我们说Rich Hickey:

" 我仍然对演员不感兴趣.他们仍然将制作人与消费者联系起来.是的,人们可以模仿或实现某些类型的队列与演员(尤其是人们经常这样做),但由于任何演员机制已经包含了队列,似乎很明显,队列更原始.应该注意的是,Clojure的并发使用状态的机制仍然可行,并且通道面向系统的流程方面. "(来源)

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

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

" 缓冲发送不会被发送者确认,并且可以任意长.缓冲的通道和goroutine非常接近actor模型.

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

  • 关于Rich Hickey的引用,我不认为队列的原始性是对演员不热情的一个很好的推理.指针比引用更原始,但我们没有为JVM设计的指针. (3认同)
  • 哦,阿纳托利.你能做的最少就是把我的名字拼写正确.:) (2认同)
  • 你好。Scala 程序员 5 年了。Akka 用户的时间大致相同。我使用演员的次数越多,我的热情就越低。当它们被过度使用时,很容易用它们创建一个非常复杂、难以理解的系统。有些事情演员做得非常好。大多数情况下,IMO 会在简单的事件循环同样或更好的情况下使用 actor。 (2认同)

Rob*_*ier 8

将我的一些评论转移到答案.这太长了:D(不要带走jamie和tolitius的帖子;它们都是非常有用的答案.)

你可以做与Akka中goroutine完全相同的事情并不完全正确.Go通道通常用作同步点.您不能直接在Akka中重现.在Akka中,后同步处理必须移动到一个单独的处理程序中(在jamie的话中用"散布"字样:D).我会说设计模式不同.你可以用a chan,做一些东西开始goroutine ,然后<-在继续前等待它完成.Akka有一种不太强大的形式ask,但ask不是真正的Akka方式IMO.

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

别误会我的意思; 我非常喜欢演员模特,我一般都喜欢Akka并且觉得工作很愉快.我也一般喜欢Go(我发现Scala很漂亮,而我觉得Go只是有用;但它非常有用).

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

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

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


Dra*_*Fax 6

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

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

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

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