斯卡拉演员:接受vs反应
首先让我说,我有相当多的Java经验,但最近才对函数式语言感兴趣。 最近我开始看Scala,这看起来像一个非常好的语言。
不过,我一直在阅读关于Scala 编程中的 Scala的Actor框架,还有一件事我不明白。 在30.4章节中,它表示使用react
而不是receive
来重用线程,这对性能是有好处的,因为在JVM中线程是很昂贵的。
这是否意味着,只要我记得要求react
而不是receive
,我可以像我喜欢的那样开始尽可能多的演员? 在发现Scala之前,我一直在玩Erlang, 编程Erlang的作者吹嘘产卵超过200,000进程没有打破汗水。 我讨厌用Java线程来做到这一点。 与Erlang(和Java)相比,我在Scala中看到了什么样的限制?
另外,这个线程如何在Scala中重用? 为了简单起见,我们假设我只有一个线程。 我会开始在这个线程中顺序运行的所有演员,还是会进行某种任务切换? 例如,如果我启动了两个互相发送消息的演员,如果他们在同一个线程中启动,我是否会冒死锁的风险?
根据斯卡拉编程 ,编写演员使用react
比receive
更困难。 这听起来似乎合理,因为react
不会返回。 然而,本书继续展示如何使用Actor.loop
在循环内部react
。 结果,你得到了
loop { react { ... } }
对我而言,这看起来很相似
while (true) { receive { ... } }
这在本书的早些时候使用。 不过,这本书说,“实际上,程序至less需要几个receive
”。 那么我在这里错过了什么? 除此之外,还有哪些能做的react
呢? 为什么我在乎
最后,谈到我不明白的核心:本书不断提及如何使用react
使得可以放弃调用堆栈来重用线程。 这是如何运作的? 为什么有必要放弃调用堆栈? 为什么当一个函数终止抛出一个exception( react
),而不是当它通过返回( receive
)终止时,可以抛弃调用栈?
我有一个印象, Scala编程一直在掩盖一些关键的问题,这是一个耻辱,因为否则它是一本非常出色的书。
首先,等待receive
每个演员都在占线。 如果它从来没有收到任何东西,该线程将永远不会做任何事 react
的演员在接收到某些东西之前不会占用任何线程。 一旦它接收到某个东西,一个线程就会被分配给它,并被初始化。
现在,初始化部分很重要。 接收线程预计会返回一些东西,反应线程不是。 所以在最后的react
结束之前的堆栈状态可以被完全抛弃。 不需要保存或恢复堆栈状态,使线程更快启动。
有各种性能的原因,为什么你可能需要一个或其他。 如你所知,在Java中有太多的线程是不是一个好主意。 另一方面,因为你必须在一个线程react
之前把一个actor附加到一个线程上,所以receive
一条消息要比对它react
要快。 所以如果你有演员收到很多信息但是做得很less, react
的额外延迟可能会让你的目的太慢。
答案是肯定的 – 如果你的演员没有阻塞你的代码中的任何东西,并且你正在使用react
,那么你可以在一个线程中运行你的“并发”程序(试着设置系统属性actors.maxPoolSize
来找出) 。
为什么有必要放弃调用堆栈的一个更明显的原因是,否则loop
方法将以StackOverflowError
结束。 实际上,框架相当聪明地通过抛出一个SuspendActorException
结束一个react
,这个SuspendActorException
被循环代码捕获,然后通过andThen
方法再次运行react
。
看看Actor
中的mkBody
方法,然后查看seq
方法,看看循环如何重新安排自己 – 非常聪明的东西!
那些“丢弃堆栈”的陈述也使我困惑了一会儿,我想我现在就明白了,这是我现在的理解。 在“接收”的情况下,在消息上有一个专用的线程阻塞(在监视器上使用object.wait()),这意味着完整的线程堆栈可用,并准备好从接收信息。 例如,如果您有以下代码
def a = 10; while (! done) { receive { case msg => println("MESSAGE RECEIVED: " + msg) } println("after receive and printing a " + a) }
线程将在接收调用中等待,直到接收到消息,然后继续打印“在接收和打印10”消息之后,在线程被阻塞之前打印在堆栈帧中的值“10”。
在反应的情况下,没有这样的专用线程,反应方法的整个方法体被捕获为闭包,并由相应的参与者接收到消息的任意线程执行。 这意味着只有那些可以作为闭包捕获的语句才会被执行,这就是返回types“Nothing”的地方。 考虑下面的代码
def a = 10; while (! done) { react { case msg => println("MESSAGE RECEIVED: " + msg) } println("after react and printing a " + a) }
如果反应有一个返回types的void,这意味着在“react”调用之后有语句是合法的(在这个例子中,println语句在“反应和打印10”之后打印消息),但实际上将永远不会被执行,因为只有“react”方法的主体被捕获并被sorting以供稍后(在消息到达时)执行。 由于反应合同的返回types为“无”,因此不能有任何反应,因此没有理由维持堆栈。 在上面的例子中,variables“a”不必被维护,因为反应调用之后的语句根本不被执行。 请注意,反应主体所需的所有variables已被捕获为闭包,因此它可以执行得很好。
Java演员框架Kilim实际上是通过保存获取展开消息的堆栈来维护堆栈。
只是为了在这里:
没有控制反转的基于事件的编程
这些论文是从Actor的scala api链接的,为actor的实现提供了理论框架。 这包括为什么可能永远不会返回。
我还没有做过scala / akka的任何重大的工作,但是我明白,在演员的安排方式有一个非常显着的差异。 阿卡只是一个聪明的线程池,是时间分割执行的演员…每一个时间片将是一个消息执行完成的演员不像在二郎可能是每个指令?
这导致我认为反应更好,因为它提示当前线程考虑其他参与者调度接收“可能”使当前线程继续为同一个演员执行其他消息的位置。