[英]Blocking calls in Akka Actors
作為一個新手,我試圖了解演員的工作方式。 而且,從文檔中,我認為我理解actor是以同步模式執行的對象,並且actor執行可以包含阻塞/同步方法調用,例如db請求
但是,我不明白的是,如果你編寫一個內部有一些阻塞調用的actor(比如阻塞查詢執行),它會搞亂整個線程池(從某種意義上說cpu利用率會下降等等) ), 對 ? 我的意思是,根據我的理解,如果/當演員進行阻止調用時,JVM無法理解它是否可以將該線程切換到其他人。
因此,考慮到並發的性質,演員不應該做任何阻塞調用嗎?
如果是這種情況,那么建議的非阻塞/異步調用方法是什么,讓我們說一個Web服務調用可以獲取內容並在該請求完成時向另一個actor發送消息? 我們應該只使用演員內部的東西:
未來地圖{response => x! 響應
這是處理這個問題的正確方法嗎?
如果你能為我澄清這一點,我將不勝感激。
這實際上取決於用例。 如果查詢不需要序列化,那么您可以在將來執行查詢並將結果發送回發件人,如下所示:
import scala.concurrent.{ future, blocking}
import akka.pattern.pipe
val resFut = future {
blocking {
executeQuery()
}
}
resFut pipeTo sender
您還可以專門為DB調用創建專用調度程序,並使用路由器創建actor。 這樣,您還可以輕松限制並發數據庫請求的數量。
真的很棒的介紹“新手的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方法中 - 使用其他異步消息發送結果。 但請注意發送方變量可能會發生變化,因此請將其關閉(在將來的對象中進行本地引用)。
ps:新手的斯卡拉指南 - 真是太棒了。
更新:(添加示例代碼)
我們有工人和經理。 經理設定要完成的工作,工人報告“得到它”並開始長時間的過程(睡眠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()
}
}
如果您正在考慮在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的多個實例並將路由器置於其前面來使其看起來像應用程序的其余部分的單個actor一樣。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.