在Scala中实现Go的并发模式难吗?

时间:2013-11-26 00:16:46

标签: scala concurrency go

毫无疑问,Go的语法比Scala简单得多。它的语言功能也较少。我非常喜欢用Go来编写并发代码的简易性。

事实证明,高性能代码是无阻塞代码(请参阅http://norvig.com/21-days.html#Answers),Go和Scala都非常擅长这一点。

我的问题是如何通过实现相同的并发模式,如何在Scala中编写与Go程序完全相同的程序。我想到的第一件事就是以与Channels类似的方式使用Futures。

我正在寻找

  • Scala中可能的Go并发模式实现
  • 如果Go构造很难在Scala中完全模拟
  • code snippets

非常感谢任何帮助。

[编辑] Go并发模式的一些例子 http://talks.golang.org/2012/concurrency.slide

扇入

func fanIn(input1, input2 <-chan string) <-chan string {
  c := make(chan string)
  go func() {
    for {
      select {
        case s := <-input1:  c <- s
        case s := <-input2:  c <- s
      }
    }
  }()
  return c
}

超时粒度(一个频道与整个对话)

在多个实例之间复制服务调用并返回第一个实例的值以进行响应。 (这是使用一组模式)

全部用:没有锁。没有条件变量。没有回调。 (Scala期货使用回调)

5 个答案:

答案 0 :(得分:6)

Go具有核心语言内置的并发功能,而Scala使用Java java.util.concurrent中的concurrent package和并发原语。

在Scala中,使用基于线程的并发或Actor Model是惯用的,而Go并发基于Hoare's Communicating Sequential Processes

虽然两种语言之间的并发原语不一样,但看起来有某些相似性。

在Go中,通常使用GoroutinesChannels来实现并发。还有其他更传统的low level synchronization primitives such as mutexes and wait groups

在Scala中,据我所知,任何声明为“Runnable”的类都将在一个单独的线程中启动,并且不会阻塞。这在功能上类似于goroutines。

在Scala中,队列可用于将类似方式的例程之间的信息传递给Go中的Channels。

编辑:正如Chuck指出的那样,“Scala的Queues和Go通道之间的关键区别在于,默认情况下,Go的通道会阻止写入,直到某些东西准备从它们读取并阻塞阅读,直到有东西准备好写给他们。“。这需要写入任何通道的Scala实现。

编辑2:正如Maurício Linhares所指出的那样,“你可以使用Async在scala中进行没有可见回调的并发 - github.com/scala/async - 但如果没有回调,你就无法做到考虑到当前实施JVM的方式,这是不可能的。“

感谢大家的建设性意见。

有关详细信息,请参阅:

答案 1 :(得分:5)

简短的回答是不,这并不难。

如您所知,通过消息传递的并发可以使用阻塞或非阻塞同步原语进行操作。 Go的频道可以同时执行 - 它们可以是无缓冲的或缓冲的 - 您可以选择。

在JVM语言中有很多关于非阻塞并发性总是优越的。一般来说是真的;它只是JVM的一个特性,它的线程非常昂贵。作为回应,大多数JVM并发API仅提供非阻塞模型,尽管这是不幸的。

对于最多1000个JVM线程的相对适度的并发性,即使在JVM上,阻塞并发也可以非常有效地工作。因为这种风格不涉及任何回调,所以很容易编写,然后再阅读。

坎特伯雷大学优秀的JCSP库是使用CSP频道编写Java / Scala / ...程序的好方法。这与Go使用的风格相同; JCSP通道与Go通道非常相似,提供了无缓冲或缓冲(或覆盖固定缓冲区)大小的选项。它的select被称为Alternative,并且已被JCSP开发人员通过正式分析证明是正确的。

但是因为JVM实际上不能支持超过1000个线程,所以这不适合某些应用领域。但是,那就是Go ......


脚注:当前版本的JCSP是Maven回购中的v1.1rc5,与JCSP网站所说的相反。

答案 2 :(得分:3)

良好的实施并非易事。

即。你可以用'阻塞'方式实现一个,其中每个去阻塞原语(通道等待)实际上会阻塞执行线程。实施将是微不足道的,但无用。

替代方案是构建一种机制,允许“暂停/恢复”执行流以异步等待。由于我们在JVM中没有内置的continuation支持,因此实现它非常复杂,需要或AST转换或字节码编织。

为了实现#1方法(即在SIP-22异步之上进行AST转换),你可以查看https://github.com/rssh/scala-gopher(警告:我是作者)。

答案 3 :(得分:1)

显然有第三方lib(Netflix)为Scala(以及Java和其他JVM语言)提供了反应式扩展。 RX的可观察量可以与Go的频道类似。

https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-scala

文档也很有用,提供了常见模式的可视化表示。

答案 4 :(得分:0)

在JVM上实现CSP样式的并发并不容易,无论是Java还是Scala。原因是CSP基于具有减少的上下文的线程,这些线程通常称为绿色线程。减少的上下文消耗更少的内存,这意味着您可以运行比OS线程或Java线程更多的绿色线程(1个Java线程对应于1个OS线程)。我曾经尝试过:使用4 GB RAM,你可以启动大约80.000 Goroutines(Go中的绿色线程的变体),而大约2.000个Java线程。

现在为什么重要? CSP中的想法是,如果某个频道不包含任何数据,那么只有&#34;只有&#34;一个绿色线程丢失,现在位于该通道,直到它收到输入。假设您有一个由40.000个用户访问的Web应用程序。可以在具有4 GB RAM的计算机上启动的80.000 Goroutines可以立即处理那些40.000连接(1个入站连接和1个出站连接)。没有绿色线程,您需要更多内存或更多服务器。

绿色主题中的另一点是,如果绿色线程位于某个频道上,您就不必担心,因为您拥有这么多线程。现在使用面向通道的代码,您可以看到真正在表面之后的代码是异步的,就好像它是同步的一样。通过通道的消息流跟随任何其他方法调用一样容易。 Robert Pike在this Youtube-Video中大约29:00的位置解释了这一点。这使得CSP风格的并发代码从一开始就更容易实现,也更容易找到与并发相关的错误。

另一个问题是延续。假设您有一个功能,它连续消耗来自2个通道的数据并以某种方式计算数据。现在,第一个通道有数据,但第二个没有。因此,当第二个通道接收数据时,语言运行时必须将函数内部跳转到第二个通道向函数提供数据的位置。为了能够做到这一点,运行时需要记住跳转的位置,并且必须将从第一个chanel中获取的数据存储在某处并恢复它,因为它与第二个通道的数据一起计算。这可以在JVM上使用continuation库来完成,这些库使用字节代码注入来进行&#34; stashing&#34;中间结果并记住跳转到的位置。可以做到这一点的Java nad Kotlin的一个库是Quasar:http://docs.paralleluniverse.co/quasar/ Quasar也有光纤,可以作为在JVM上使用与绿色线程相似的方法。 Quasar的开发人员是Ron Pressler,他被Oracle雇佣到Projekt Loom工作:http://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html这个项目的想法是支持JVM级别的光纤和延续,这将使光纤更高效并且字节代码持续注射不那么累赘。

然后在Kotlin中也有Coroutines:https://kotlinlang.org/docs/reference/coroutines.html Kotlin的Couroutines也实现了Kotlin编译器提供的光纤和延续,因此开发人员不需要协助CSP库(例如Quasar)知道什么函数需要字节码注入。

不幸的是,Kotlin的Couroutines仅适用于Kotlin,不能在其外使用。因此它们不适用于其他JVM语言。 Quasar不能与Scala一起使用,因为Scala for continuation的字节代码注入比Java或Kotlin要困难得多,因为Scala是一种更复杂的语言。至少这是Quasar开发商提供的推理。

所以最好的事情就像Scala所关注的那样是坚持Akka或等待Project Loom完成。然后,一些Scala人员可以在真正实现CSP的级别上开始为Scala实现CSP。在撰写本文时,Project Loom正在运行,但尚未得到Oracle的正式批准。因此,目前尚不清楚未来的JDK是否会包含全面CSP所需的内容。