作为一个新手,我试图了解演员如何运作。并且,从文档中,我认为我理解actor是以同步模式执行的对象,并且actor执行可以包含阻塞/同步方法调用,例如,数据库请求
但是,我不明白的是,如果你编写一个内部有一些阻塞调用的actor(比如阻塞查询执行),它会搞乱整个线程池(从某种意义上说cpu利用率会下降)等等),对吗?我的意思是,根据我的理解,JVM无法理解是否可以将该线程切换到其他人,如果/当演员进行阻止调用时。
因此,鉴于并发性,Actors不应该做任何阻塞调用吗?
如果是这种情况,建议使用非阻塞/异步调用的方法是什么,让我们说一个Web服务调用,它取出一些内容并在该请求完成时向另一个actor发送消息?我们应该只使用演员内部的东西:
future map {response => X ! response.body}
这是处理此问题的正确方法吗?
如果您能为我澄清这一点,我将不胜感激。
答案 0 :(得分:16)
这实际上取决于用例。如果查询不需要序列化,那么您可以在将来执行查询并将结果发送回发件人,如下所示:
import scala.concurrent.{ future, blocking}
import akka.pattern.pipe
val resFut = future {
blocking {
executeQuery()
}
}
resFut pipeTo sender
您还可以专门为数据库调用创建专用调度程序,并使用路由器创建actor。这样,您还可以轻松限制并发数据库请求的数量。
答案 1 :(得分:15)
非常棒的介绍“新手的Scala指南第14部分:并发的演员方法”http://danielwestheide.com/blog/2013/02/27/the-neophytes-guide-to-scala-part-14-the-actor-approach-to-concurrency.html。
Actor接收消息,将阻塞代码包装到将来,在它的Future.onSuccess方法中 - 使用其他异步消息发送结果。但要注意发送方变量可能会发生变化,因此请将其关闭(在未来的对象中进行本地引用)。
p.s。:新手的斯卡拉指南 - 非常棒的书。
更新:(添加示例代码)
我们有工人和经理。经理设定要完成的工作,工人报告“得到它”并开始长时间的过程(睡眠1000)。同时系统ping经理,消息“活着”,经理用工作人员打电话给他们。完成工作后 - 工作人员会通知经理。
注意:在导入的“默认/全局”线程池执行程序中执行sleep 1000 - 您可以获得线程饥饿。 注意:val commander = sender需要“关闭”对原始发件人的引用,因为当onSuccess将被执行时 - actor中的当前发件人可能已经设置为其他'发件人'......
日志:
01:35:12:632 Humming ...
01:35:12:633 manager: flush sent
01:35:12:633 worker: got command
01:35:12:633 manager alive
01:35:12:633 manager alive
01:35:12:633 manager alive
01:35:12:660 worker: started
01:35:12:662 worker: alive
01:35:12:662 manager: resource allocated
01:35:12:662 worker: alive
01:35:12:662 worker: alive
01:35:13:661 worker: done
01:35:13:663 manager: work is done
01:35:17:633 Shutdown!
代码:
import akka.actor.{Props, ActorSystem, ActorRef, Actor}
import com.typesafe.config.ConfigFactory
import java.text.SimpleDateFormat
import java.util.Date
import scala.concurrent._
import ExecutionContext.Implicits.global
object Sample {
private val fmt = new SimpleDateFormat("HH:mm:ss:SSS")
def printWithTime(msg: String) = {
println(fmt.format(new Date()) + " " + msg)
}
class WorkerActor extends Actor {
protected def receive = {
case "now" =>
val commander = sender
printWithTime("worker: got command")
future {
printWithTime("worker: started")
Thread.sleep(1000)
printWithTime("worker: done")
}(ExecutionContext.Implicits.global) onSuccess {
// here commander = original sender who requested the start of the future
case _ => commander ! "done"
}
commander ! "working"
case "alive?" =>
printWithTime("worker: alive")
}
}
class ManagerActor(worker: ActorRef) extends Actor {
protected def receive = {
case "do" =>
worker ! "now"
printWithTime("manager: flush sent")
case "working" =>
printWithTime("manager: resource allocated")
case "done" =>
printWithTime("manager: work is done")
case "alive?" =>
printWithTime("manager alive")
worker ! "alive?"
}
}
def main(args: Array[String]) {
val config = ConfigFactory.parseString("" +
"akka.loglevel=DEBUG\n" +
"akka.debug.lifecycle=on\n" +
"akka.debug.receive=on\n" +
"akka.debug.event-stream=on\n" +
"akka.debug.unhandled=on\n" +
""
)
val system = ActorSystem("mine", config)
val actor1 = system.actorOf(Props[WorkerActor], "worker")
val actor2 = system.actorOf(Props(new ManagerActor(actor1)), "manager")
actor2 ! "do"
actor2 ! "alive?"
actor2 ! "alive?"
actor2 ! "alive?"
printWithTime("Humming ...")
Thread.sleep(5000)
printWithTime("Shutdown!")
system.shutdown()
}
}
答案 2 :(得分:1)
如果您正在考虑在Akka中阻止呼叫,那么考虑线程池是正确的。您执行的阻塞越多,您需要的线程池越大。完全非阻塞系统实际上只需要一个等于机器CPU核心数的线程池。参考配置使用机器上CPU核心数量的3倍的池来允许一些阻塞:
# The core pool size factor is used to determine thread pool core size
# using the following formula: ceil(available processors * factor).
# Resulting size is then bounded by the core-pool-size-min and
# core-pool-size-max values.
core-pool-size-factor = 3.0
但是,如果您执行更多阻止,您可能希望将akka.default-dispatcher.fork-join-executor.core-pool-size-factor
增加到更高的数字,或者使用非默认的调度程序专门用于阻止具有更高fork-join-executor.core-pool-size-factor
WRT在Akka中阻止呼叫的最佳方法是什么。我建议通过制作阻止调用的actor的多个实例并在其前面放置router来使它们看起来像应用程序的其余部分的单个actor一样来扩展。