Scala演员:接受vs反应

jqn*_*qno 110 multithreading scala actor

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

但是,我一直在阅读Scala 编程中的 Scala的Actor框架,有一点我不明白.在第30.4章中,它表示使用react而不是receive可以重用线程,这对性能有好处,因为线程在JVM中很昂贵.

这是否意味着,只要我记得打电话react而不是receive,我可以开始尽可能多的演员?在发现Scala之前,我一直在和Erlang一起玩,编程Erlang的作者自豪地生成了超过20万个进程,而且不会出汗.我讨厌用Java线程做到这一点.与Erlang(和Java)相比,我在Scala中看到了什么样的限制?

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

根据Scala编程,编写演员使用react比使用更困难receive.这听起来似乎有道理,因为react不会回来.然而,本书接着展示了如何react使用循环内部Actor.loop.结果,你得到了

loop {
    react {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

对我来说,这看起来非常相似

while (true) {
    receive {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

在本书前面使用过.尽管如此,这本书还是说"在实践中,程序至少需要少数几个receive".那我在这里错过了什么?除了回归之外,receive做什么不能做到react?为什么我关心?

最后,进入我不理解的核心:本书不断提及如何使用react可以丢弃调用堆栈来重用线程.这是如何运作的?为什么有必要丢弃调用堆栈?当函数通过抛出异常(react)而终止时,为什么可以放弃调用堆栈,而不是当它通过返回(receive)终止时?

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

Dan*_*ral 78

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

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

您可能需要其中一个或多个的各种性能原因.如您所知,Java中包含太多线程并不是一个好主意.另一方面,因为你必须先将一个actor附加到一个线程react,所以它对receive消息的速度要快react于它.因此,如果您的演员收到许多消息但很少使用它,那么额外的延迟react可能会使您的目的太慢.


oxb*_*kes 21

答案是"是" - 如果你的演员没有阻止代码中的任何东西并且你正在使用react,那么你可以在一个线程中运行你的"并发"程序(尝试设置系统属性actors.maxPoolSize以找出).

必要丢弃调用堆栈的一个更明显的原因是,否则该loop方法将以a结尾StackOverflowError.实际上,框架相当巧妙地react通过抛出a来结束SuspendActorException,循环代码然后react通过该andThen方法再次运行.

看看mkBody方法Actor,然后seq看方法,看看循环如何重新安排自己 - 非常聪明的东西!


Ash*_*win 20

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

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

线程将在接收呼叫中等待,直到收到消息,然后继续打开并在线程被阻止之前打印"接收并打印10"消息并且值为"10".

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

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

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

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


小智 8

只是在这里:

基于事件的无控制反转编程

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