3 回答
TA贡献1821条经验 获得超6个赞
这里有两个问题:
Scala 是移植的好选择
goroutines
吗?
这是一个简单的问题,因为 Scala 是一种通用语言,它并不比您可以选择“移植 goroutines”的许多其他语言更差或更好。
当然,关于为什么 Scala作为一种语言更好或更差的原因有很多意见(例如,这是我的),但这些只是意见,不要让它们阻止您。由于 Scala 是通用的,它“几乎”归结为:您可以在语言 X 中做的一切,您都可以在 Scala 中做。如果这听起来太宽泛了…… Java 中的延续怎么样:)
Scala 演员与
goroutines
?
唯一的相似之处(除了吹毛求疵)是它们都与并发和消息传递有关。但这就是相似性结束的地方。
由于 Jamie 的回答很好地概述了 Scala 演员,因此我将更多地关注 Goroutines/core.async,但会介绍一些演员模型。
演员帮助事情“无忧分发”
其中“无忧”片通常与诸如有关:fault tolerance
,resiliency
,availability
等。
无需深入研究 actor 的工作原理,简单来说,actor 与以下两个术语有关:
位置:每个参与者都有一个地址/引用,其他参与者可以使用它来向其发送消息
行为:当消息到达参与者时应用/调用的函数
想想“谈话进程”,其中每个进程都有一个引用和一个在消息到达时被调用的函数。
当然还有更多内容(例如查看Erlang OTP或akka docs),但以上两个是一个好的开始。
演员有趣的地方是......实现。目前有两个大的,是 Erlang OTP 和 Scala AKKA。虽然他们都旨在解决同一件事,但还是存在一些差异。让我们看一对夫妇:
我故意不使用诸如“引用透明”、“幂等”之类的术语。它们除了造成混乱之外没有任何好处,所以让我们只谈一下不变性[一个
can't change that
概念]。Erlang 作为一种语言是固执己见的,它倾向于强大的不变性,而在 Scala 中,当接收到消息时,很容易让 actor 改变/改变他们的状态。不推荐这样做,但 Scala 中的可变性就在您面前,人们确实在使用它。Joe Armstrong 谈到的另一个有趣的观点是 Scala/AKKA 受到 JVM 的限制,JVM 的设计并没有真正考虑到“分布式”,而 Erlang VM 是。它与许多事情有关,例如:进程隔离、每个进程与整个 VM 垃圾收集、类加载、进程调度等。
上面的重点并不是说一个比另一个更好,而是表明actor模型作为一个概念的纯度取决于它的实现。
现在到 goroutines ..
Goroutines 有助于按顺序推理并发
正如已经提到的其他答案,goroutines 植根于Communicating Sequential Processes,这是一种“用于描述并发系统中交互模式的正式语言”,根据定义,它几乎可以意味着任何事情:)
我将给出基于core.async 的示例,因为我比 Goroutines 更了解它的内部结构。但是core.async
是在 Goroutines/CSP 模型之后构建的,所以概念上应该不会有太多差异。
core.async/Goroutine 中的主要并发原语是一个channel
. 将 achannel
视为“岩石上的队列”。该通道用于“传递”消息。任何想要“参与游戏”的进程都会创建或获取对 a 的引用,并向其channel
发送/接收(例如发送/接收)消息。
24 小时免费停车
在通道上完成的大部分工作通常发生在“ Goroutine ”或“ go block ”中,它“获取它的主体并检查它是否有任何通道操作。它将把主体变成一个状态机。在到达任何阻塞操作时,状态机将被“停放”并释放实际的控制线程。这种方法类似于 C# async 中使用的方法。当阻塞操作完成时,代码将被恢复(在线程池线程上,或JS VM 中的唯一线程) ”(源代码)。
用视觉传达要容易得多。以下是阻塞 IO 执行的样子:
您可以看到线程大部分时间都在等待工作。这是相同的工作,但通过“Goroutine”/“go block”方法完成:
这里 2 个线程完成了所有工作,4 个线程以阻塞方式完成了所有工作,同时花费了相同的时间。
上面描述中的关键是:当它们没有工作时“线程被停放”,这意味着,它们的状态被“卸载”到状态机,而实际的实时 JVM 线程可以自由地做其他工作(一个伟大的视觉来源)
注意:在core.async 中,通道可以在“go 块”之外使用,这将由没有停放能力的JVM 线程支持:例如,如果它阻塞,它将阻塞真正的线程。
Go 频道的力量
“Goroutines”/“go blocks”中另一个重要的事情是可以在通道上执行的操作。例如,可以创建一个超时通道,它将在 X 毫秒内关闭。或选择/ alt!功能,当与许多频道结合使用时,就像跨不同频道的“你准备好了吗”轮询机制一样。将其视为非阻塞 IO 中的套接字选择器。以下是使用timeout channel和的示例alt!:
(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 可能不是您的第一语言,但您不能不同意这种并发实现的顺序看起来和感觉如何。
您还可以从/向多个通道合并/扇入/扇出数据,映射/减少/过滤/...通道数据等等。频道也是一等公民:您可以将频道传递给频道..
去 UI 去!
由于 core.async “go blocks” 具有“停放”执行状态的能力,并且在处理并发时具有非常顺序的“外观和感觉”,那么 JavaScript 呢?JavaScript 中没有并发,因为只有一个线程,对吧?模拟并发的方式是通过 1024 个回调。
但它不一定是这样。来自wracer的上述示例实际上是用 ClojureScript 编写的,可编译为 JavaScript。是的,它可以在具有多个线程的服务器上和/或在浏览器中工作:代码可以保持不变。
Goroutines 与 core.async
同样,一些实现差异[还有更多]强调了一个事实,即理论概念在实践中并不完全是一对一的:
在 Go 中,通道是类型化的,而在 core.async 中则不是:例如,在 core.async 中,您可以将任何类型的消息放在同一个通道上。
在 Go 中,你可以将可变的东西放在通道上。不推荐,但你可以。在 core.async 中,由 Clojure 设计,所有数据结构都是不可变的,因此通道内的数据感觉更安全。
那么判决结果是什么?
我希望以上内容可以说明演员模型和 CSP 之间的差异。
不是为了引起一场火焰战争,而是为了给你另一个视角,让我们说 Rich Hickey:
"我对演员仍然不感兴趣。他们仍然将生产者与消费者结合起来。是的,可以用演员模拟或实现某些类型的队列(值得注意的是,人们经常这样做),但由于任何演员机制都已经包含了队列,因此显然队列更原始。应该注意的是,Clojure 的并发使用状态的机制仍然可行,并且通道面向系统的流方面。 ”
但是,在实践中,Whatsapp 是基于 Erlang OTP 的,它似乎卖得很好。
另一个有趣的引用来自 Rob Pike:
"缓冲发送不向发送方确认并且可以花费任意长的时间。缓冲通道和 goroutines 非常接近 actor 模型。
Actor 模型和 Go 之间的真正区别在于通道是一等公民。同样重要的是:它们是间接的,就像文件描述符而不是文件名,允许在 actor 模型中不容易表达的并发样式。也有相反的情况;我不是在做价值判断。理论上,这些模型是等效的。“
TA贡献1963条经验 获得超6个赞
将我的一些评论移到答案中。时间太长了:D(不要从 jamie 和 tolitius 的帖子中删除;他们都是非常有用的答案。)
在 Akka 中使用 goroutine 可以做完全相同的事情,这并不完全正确。Go 通道通常用作同步点。你不能直接在 Akka 中重现它。在 Akka 中,同步后处理必须移动到单独的处理程序中(用 Jamie 的话来说是“散布”:D)。我会说设计模式是不同的。你可以用 a 启动一个 goroutine chan
,做一些事情,然后<-
等待它完成再继续。Akka 有一个不那么强大的形式ask
,但ask
并不是真正的 Akka 方式 IMO。
Chans 也被输入,而邮箱则不是。这对 IMO 来说是件大事,对于基于 Scala 的系统来说,这是非常令人震惊的。我知道这become
很难用类型化的消息来实现,但这可能表明它become
不太像 Scala。关于 Akka,我可以这么说。通常感觉就像它自己的东西恰好在 Scala 上运行。Goroutines 是 Go 存在的一个关键原因。
不要误会我的意思;我非常喜欢 actor 模型,我通常喜欢 Akka 并且觉得在其中工作很愉快。我也普遍喜欢 Go(我觉得 Scala 很漂亮,虽然我觉得 Go 只是有用;但它非常有用)。
但容错确实是 Akka IMO 的重点。你碰巧获得了并发性。并发是 goroutine 的核心。容错在 Go 中是一个单独的东西,委托给defer
和recover
,可以用来实现相当多的容错。Akka 的容错更正式、功能更丰富,但也可能更复杂一些。
所有人都说,尽管有一些短暂的相似之处,但 Akka 不是 Go 的超集,它们在功能上有很大的不同。Akka 和 Go 在鼓励你解决问题的方式上有很大不同,而在一个方面容易的事情在另一个方面很尴尬、不切实际,或者至少是不习惯的。这是任何系统中的关键区别。
因此,将其带回您的实际问题:我强烈建议在将 Go 接口引入 Scala 或 Akka(这也是 IMO 完全不同的东西)之前重新考虑 Go 接口。确保你按照目标环境的方式做事。一个复杂的 Go 库的直接移植很可能不适合这两种环境。
- 3 回答
- 0 关注
- 444 浏览
添加回答
举报