Scala 演员:接收与反应

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1251666/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:34:53  来源:igfitidea点击:

Scala actors: receive vs react

scalamultithreadingactor

提问by jqno

Let me first say that I have quite a lot of Java experience, but have only recently become interested in functional languages. Recently I've started looking at Scala, which seems like a very nice language.

首先让我说我有很多 Java 经验,但最近才对函数式语言感兴趣。最近我开始研究 Scala,它看起来是一种非常好的语言。

However, I've been reading about Scala's Actor framework in Programming in Scala, and there's one thing I don't understand. In chapter 30.4 it says that using reactinstead of receivemakes it possible to re-use threads, which is good for performance, since threads are expensive in the JVM.

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

Does this mean that, as long as I remember to call reactinstead of receive, I can start as many Actors as I like? Before discovering Scala, I've been playing with Erlang, and the author of Programming Erlangboasts about spawning over 200,000 processes without breaking a sweat. I'd hate to do that with Java threads. What kind of limits am I looking at in Scala as compared to Erlang (and Java)?

这是否意味着,只要我记得调用react而不是receive,我就可以启动任意数量的 Actors ?在发现 Scala 之前,我一直在玩 Erlang,编程 Erlang的作者吹嘘自己毫不费力地生成了超过 200,000 个进程。我不想用 Java 线程这样做。与 Erlang(和 Java)相比,我在 Scala 中看到了什么样的限制?

Also, how does this thread re-use work in Scala? Let's assume, for simplicity, that I have only one thread. Will all the actors that I start run sequentially in this thread, or will some sort of task-switching take place? For example, if I start two actors that ping-pong messages to each other, will I risk deadlock if they're started in the same thread?

另外,这个线程如何在 Scala 中重用?为简单起见,让我们假设我只有一个线程。我开始的所有 actor 会在这个线程中按顺序运行,还是会发生某种任务切换?例如,如果我启动两个相互发送乒乓消息的actor,如果他们在同一个线程中启动,我会面临死锁的风险吗?

According to Programming in Scala, writing actors to use reactis more difficult than with receive. This sounds plausible, since reactdoesn't return. However, the book goes on to show how you can put a reactinside a loop using Actor.loop. As a result, you get

根据Scala 编程,编写要使用的 actorreact比使用receive. 这听起来似乎有道理,因为react它不会返回。然而,这本书继续展示如何react使用Actor.loop. 结果,你得到

loop {
    react {
        ...
    }
}

which, to me, seems pretty similar to

对我来说,这似乎与

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

which is used earlier in the book. Still, the book says that "in practice, programs will need at least a few receive's". So what am I missing here? What can receivedo that reactcannot, besides return? And why do I care?

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

Finally, coming to the core of what I don't understand: the book keeps mentioning how using reactmakes it possible to discard the call stack to re-use the thread. How does that work? Why is it necessary to discard the call stack? And why can the call stack be discarded when a function terminates by throwing an exception (react), but not when it terminates by returning (receive)?

最后,说到我不明白的核心:书中不断提到使用如何react可以丢弃调用堆栈以重新使用线程。这是如何运作的?为什么需要丢弃调用栈?为什么在函数通过抛出异常(react)终止时可以丢弃调用堆栈,而在通过返回(receive)终止时则不能丢弃调用堆栈?

I have the impression that Programming in Scalahas been glossing over some of the key issues here, which is a shame, because otherwise it's a truly excellent book.

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

采纳答案by Daniel C. Sobral

First, each actor waiting on receiveis occupying a thread. If it never receives anything, that thread will never do anything. An actor on reactdoes not occupy any thread until it receives something. Once it receives something, a thread gets allocated to it, and it is initialized in it.

首先,每个等待的actorreceive都占用一个线程。如果它从不接收任何东西,则该线程将永远不会做任何事情。一个actor在react接收到一些东西之前不会占用任何线程。一旦它接收到一些东西,一个线程就会被分配给它,并在其中初始化。

Now, the initialization part is important. A receiving thread is expected to return something, a reacting thread is not. So the previous stack state at the end of the last reactcan be, and is, wholly discarded. Not needing to either save or restore the stack state makes the thread faster to start.

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

There are various performance reasons why you might want one or other. As you know, having too many threads in Java is not a good idea. On the other hand, because you have to attach an actor to a thread before it can react, it is faster to receivea message than reactto it. So if you have actors that receive many messages but do very little with it, the additional delay of reactmight make it too slow for your purposes.

您可能需要一个或另一个的性能原因有多种。如您所知,在 Java 中拥有太多线程并不是一个好主意。另一方面,因为您必须先将actor 附加到线程react,所以它receive比消息更快react。因此,如果您的 actor 接收了很多消息但对其执行的操作很少,那么额外的延迟react可能会使您的目的变得太慢。

回答by oxbow_lakes

The answer is "yes" - if your actors are not blocking on anything in your code and you are using react, then you can run your "concurrent"program within a single thread (try setting the system property actors.maxPoolSizeto find out).

答案是“是”——如果您的 actor 没有阻塞代码中的任何内容并且您正在使用react,那么您可以在单个线程中运行您的“并发”程序(尝试设置系统属性actors.maxPoolSize以找出答案)。

One of the more obvious reasons why it is necessary to discard the call stackis that otherwise the loopmethod would end in a StackOverflowError. As it is, the framework rather cleverly ends a reactby throwing a SuspendActorException, which is caught by the looping code which then runs the reactagain via the andThenmethod.

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

Have a look at the mkBodymethod in Actorand then the seqmethod to see how the loop reschedules itself - terribly clever stuff!

查看 中的mkBody方法Actor,然后seq查看循环如何重新调度自身的方法 - 非常聪明的东西!

回答by Ashwin

Those statements of "discarding the stack" confused me also for a while and I think I get it now and this is my understanding now. In case of "receive" there is a dedicated thread blocking on the message (using object.wait() on a monitor) and this means that the complete thread stack is available and ready to continue from the point of "waiting" on receiving a message. For example if you had the following code

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

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

the thread would wait in the receive call until the message is received and then would continue on and print the "after receive and printing a 10" message and with the value of "10" which is in the stack frame before the thread blocked.

线程将在接收调用中等待,直到接收到消息,然后将继续打印“接收并打印 10 后”消息,其值为“10”,该值位于线程阻塞之前的堆栈帧中。

In case of react there is no such dedicated thread, the whole method body of the react method is captured as a closure and is executed by some arbitrary thread on the corresponding actor receiving a message. This means only those statements that can be captured as a closure alone will be executed and that's where the return type of "Nothing" comes to play. Consider the following code

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

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

If react had a return type of void, it would mean that it is legal to have statements after the "react" call ( in the example the println statement that prints the message "after react and printing a 10"), but in reality that would never get executed as only the body of the "react" method is captured and sequenced for execution later (on the arrival of a message). Since the contract of react has the return type of "Nothing" there cannot be any statements following react, and there for there is no reason to maintain the stack. In the example above variable "a" would not have to be maintained as the statements after the react calls are not executed at all. Note that all the needed variables by the body of react is already be captured as a closure, so it can execute just fine.

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

The java actor framework Kilimactually does the stack maintenance by saving the stack which gets unrolled on the react getting a message.

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

回答by Hexren

Just to have it here:

只是在这里拥有它:

Event-Based Programming without Inversion of Control

没有控制反转的基于事件的编程

These papers are linked from the scala api for Actor and provide the theoretical framework for the actor implementation. This includes why react may never return.

这些论文链接自 Actor 的 scala api,并提供了 Actor 实现的理论框架。这包括为什么 react 可能永远不会返回。

回答by user3407472

I haven't done any major work with scala /akka, however i understand that there is a very significant difference in the way actors are scheduled. Akka is just a smart threadpool which is time slicing execution of actors... Every time slice will be one message execution to completion by an actor unlike in Erlang which could be per instruction?!

我还没有对 scala /akka 做过任何重大的工作,但是我知道演员的安排方式有很大的不同。Akka 只是一个智能线程池,它是对 actor 的时间切片执行......每个时间片都将是一个由 actor 完成的消息执行,这与 Erlang 中的每条指令不同?!

This leads me to think that react is better as it hints the current thread to consider other actors for scheduling where as receive "might" engage the current thread to continue executing other messages for the same actor.

这让我认为 react 更好,因为它提示当前线程考虑其他参与者进行调度,而接收“可能”参与当前线程以继续为同一参与者执行其他消息。