首先我要说的是,我有很多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
没有回来。但是,本书继续介绍如何使用react
将Actor.loop
放入循环中。结果,你得到了
loop {
react {
...
}
}
对我而言,似乎非常类似于
while (true) {
receive {
...
}
}
在本书前面使用过。尽管如此,该书还是说“在实践中,程序至少需要少量receive
”。那我在这里错过了什么? receive
除了返回之外react
不能做什么?我为什么要关心?
最后,谈到我不理解的核心:本书不断提到如何使用react
使得丢弃调用堆栈以重用线程成为可能。这是如何运作的?为什么有必要丢弃调用堆栈?为什么在函数通过抛出异常(react
)而终止时会丢弃调用堆栈,而不是在它通过返回(receive
)终止时抛弃?
我的印象是 Scala编程一直在掩盖这里的一些关键问题,这是一种耻辱,因为否则它是一本真正优秀的书。
答案 0 :(得分:78)
首先,等待receive
的每个演员都占据一个主题。如果它从未收到任何东西,该线程将永远不会做任何事情。 react
上的演员在收到某些内容之前不会占用任何线程。一旦收到某个东西,就会为它分配一个线程,并在其中初始化。
现在,初始化部分很重要。接收线程应该返回一些东西,一个反应线程不会。因此,最后react
末尾的先前堆栈状态可以完全丢弃。无需保存或恢复堆栈状态使线程更快启动。
您可能需要其中一个或多个的各种性能原因。如您所知,Java中包含太多线程并不是一个好主意。另一方面,因为你必须先将一个actor附加到一个线程react
之前,所以receive
一条消息比react
更快。react
。因此,如果您的演员收到许多消息,但很少使用它,{{1}}的额外延迟可能会使其太慢而无法达到您的目的。
答案 1 :(得分:21)
答案是“是” - 如果您的演员未阻止代码中的任何内容并且您使用的是react
,那么您可以在单个内部运行“并发”程序线程(尝试设置系统属性actors.maxPoolSize
以查找)。
必须丢弃调用堆栈的一个更明显的原因是,loop
方法将以StackOverflowError
结束。实际上,框架通过抛出react
来巧妙地结束SuspendActorException
,react
由循环代码捕获,然后通过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执行其他消息。