Scala演员:接受vs反应

时间:2009-08-09 16:14:14

标签: scala multithreading actor

首先我要说的是,我有很多Java经验,但最近才对函数式语言感兴趣。最近我开始关注Scala,这似乎是一种非常好的语言。

但是,我一直在Programming in Scala阅读有关Scala的Actor框架,有一件事我不明白。在第30.4章中,它说使用react而不是receive可以重用线程,这对性能有好处,因为线程在JVM中很昂贵。

这是否意味着,只要我记得拨打react而不是receive,我就可以开始尽可能多的演员?在发现Scala之前,我一直在和Erlang一起玩,Programming Erlang的作者吹嘘自己产生了超过200,000个进程而不会出汗。我讨厌用Java线程做到这一点。与Erlang(和Java)相比,我在Scala中看到了什么样的限制?

此外,此线程如何在Scala中重用?为简单起见,我们假设我只有一个线程。我开始的所有演员都会在这个帖子中按顺序运行,还是会进行某种任务切换?例如,如果我启动两个互相ping消息的actor,如果它们在同一个线程中启动,我会冒死锁吗?

根据Scala中的编程,使用react编写演员比使用receive更困难。这听起来似乎有道理,因为react没有回来。但是,本书继续介绍如何使用reactActor.loop放入循环中。结果,你得到了

loop {
    react {
        ...
    }
}

对我而言,似乎非常类似于

while (true) {
    receive {
        ...
    }
}

在本书前面使用过。尽管如此,该书还是说“在实践中,程序至少需要少量receive”。那我在这里错过了什么? receive除了返回之外react不能做什么?我为什么要关心?

最后,谈到我不理解的核心:本书不断提到如何使用react使得丢弃调用堆栈以重用线程成为可能。这是如何运作的?为什么有必要丢弃调用堆栈?为什么在函数通过抛出异常(react)而终止时会丢弃调用堆栈,而不是在它通过返回(receive)终止时抛弃?

我的印象是 Scala编程一直在掩盖这里的一些关键问题,这是一种耻辱,因为否则它是一本真正优秀的书。

5 个答案:

答案 0 :(得分:78)

首先,等待receive的每个演员都占据一个主题。如果它从未收到任何东西,该线程将永远不会做任何事情。 react上的演员在收到某些内容之前不会占用任何线程。一旦收到某个东西,就会为它分配一个线程,并在其中初始化。

现在,初始化部分很重要。接收线程应该返回一些东西,一个反应线程不会。因此,最后react末尾的先前堆栈状态可以完全丢弃。无需保存或恢复堆栈状态使线程更快启动。

您可能需要其中一个或多个的各种性能原因。如您所知,Java中包含太多线程并不是一个好主意。另一方面,因为你必须先将一个actor附加到一个线程react之前,所以receive一条消息比react更快。react。因此,如果您的演员收到许多消息,但很少使用它,{{1}}的额外延迟可能会使其太慢而无法达到您的目的。

答案 1 :(得分:21)

答案是“是” - 如果您的演员未阻止代码中的任何内容并且您使用的是react,那么您可以在单个内部运行“并发”程序线程(尝试设置系统属性actors.maxPoolSize以查找)。

必须丢弃调用堆栈的一个更明显的原因是,loop方法将以StackOverflowError结束。实际上,框架通过抛出react来巧妙地结束SuspendActorExceptionreact由循环代码捕获,然后通过andThen方法再次运行mkBody

查看Actor中的 seq 方法,然后查看 {{1}} 方法,了解循环如何重新安排自己 - 非常聪明的东西!

答案 2 :(得分:20)

那些“丢弃堆栈”的陈述让我困惑了一段时间,我想我现在明白了,这是我现在的理解。在“接收”的情况下,在消息上有一个专用的线程阻塞(在监视器上使用object.wait()),这意味着完整的线程堆栈可用并准备好从接收到“等待”的点继续信息。 例如,如果您有以下代码

  def a = 10;
  while (! done)  {
     receive {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after receive and printing a " + a)
  }

线程将在接收呼叫中等待,直到收到消息,然后继续并打印“接收并打印10”消息后,并在线程之前的堆栈帧中显示值“10”受阻。

如果反应没有这样的专用线程,则react方法的整个方法体被捕获为闭包,并由接收消息的相应actor上的某个任意线程执行。这意味着只有那些可以作为闭包单独捕获的语句才会被执行,并且这就是“Nothing”的返回类型所在的位置。请考虑以下代码

  def a = 10;
  while (! done)  {
     react {
        case msg =>  println("MESSAGE RECEIVED: " + msg)
     }
     println("after react and printing a " + a) 
  }

如果react有返回类型的void,则意味着在“react”调用之后有语句是合法的(在示例中println语句打印消息“在反应并打印10”后),但是实际上,永远不会执行,因为只有“反应”方法的主体被捕获并排序以便稍后执行(在消息到达时)。由于react的契约具有返回类型“Nothing”,因此不能有任何语句响应,并且没有理由维持堆栈。在上面的示例中,变量“a”不必保持为在根本不执行react调用之后的语句。请注意,反应主体所需的所有变量已经被捕获为闭包,因此它可以执行得很好。

java actor框架Kilim实际上通过保存堆栈来进行堆栈维护,该堆栈在获取消息的反应中展开。

答案 3 :(得分:8)

只是在这里:

Event-Based Programming without Inversion of Control

这些论文与演员的scala api相关联,并为演员的实现提供了理论框架。这包括为什么反应可能永远不会回来。

答案 4 :(得分:0)

我没有对scala / akka做过任何重大工作,但我知道演员的安排方式存在很大差异。 Akka只是一个聪明的线程池,是时间切片演员的执行... 每个时间片都是一个消息执行完成的演员不像Erlang那样可能是每个指令吗?!

这使我认为反应更好,因为它暗示当前线程考虑其他参与者进行调度,其中接收“可能”参与当前线程以继续为同一个actor执行其他消息。