web-dev-qa-db-ja.com

Akkaアクターでの呼び出しのブロック

初心者として、俳優のしくみを理解しようとしています。そして、ドキュメントから、アクターは同期モードで実行されるオブジェクトであり、アクターの実行にはブロッキング/同期メソッド呼び出しを含めることができることも理解していると思います。 dbリクエスト

しかし、私が理解していないのは、内部にいくつかのブロッキング呼び出し(ブロッキングクエリの実行など)を持つアクターを記述すると、スレッドプール全体を混乱させることです(CPU使用率が低下するという意味など)。 )、 正しい ?つまり、私の理解では、アクターがブロッキング呼び出しを行う場合、JVMがそのスレッドを他の誰かに切り替えることができるかどうかをJVMが理解する方法はありません。

それで、並行性の性質を考えると、アクターがこれまでにブロッキング呼び出しをしてはならないことは明らかではないでしょうか?

それが事実である場合、非ブロッキング/非同期呼び出しを行うための推奨される方法は何ですか、その要求が完了したときに何かをフェッチし、別のアクターにメッセージを送信するWebサービス呼び出しを考えてみましょう。アクター内で次のようなものを使用する必要があります。

将来のマップ{応答=> x! response.body}

これはこれを処理する適切な方法ですか?

これを明確にしていただければ幸いです。

30
Aysu Dogma

それは本当にユースケースに依存します。クエリをシリアル化する必要がない場合は、後でクエリを実行し、次のように結果を送信者に送り返すことができます。

import scala.concurrent.{ future, blocking}
import akka.pattern.pipe

val resFut = future {
  blocking {
    executeQuery()
  }
}

resFut pipeTo sender

DB呼び出し専用の専用ディスパッチャを作成し、ルーターを使用してアクターを作成することもできます。このようにして、並行DBリクエストの数を簡単に制限することもできます。

16
drexin

本当に素晴らしいイントロ「The Neophyte's Guide to Scala Part 14:The Actor Approach to Concurrency」 http://danielwestheide.com/blog/2013/02/27/the-neophytes -guide-to-scala-part-14-the-actor-approach-to-concurrency.html

アクターはメッセージを受信し、ブロッキングコードをFutureにラップします。Future.onSuccessメソッドで、他の非同期メッセージを使用して結果を送信します。ただし、センダー変数が変更される可能性があることに注意してください。そのため、変数を閉じます(将来のオブジェクトでローカル参照を作成します)。

pS:ScalaへのNeophyteのガイド-本当に素晴らしい本。

更新:(追加されたサンプルコード)

労働者とマネージャーがいます。マネージャーは実行する作業を設定し、ワーカーは「了解」と報告し、長いプロセスを開始します(スリープ1000)。一方、システムはメッセージを「生きている」とマネージャーにpingし、マネージャーはそれらとワーカーをpingします。作業が完了すると、ワーカーがマネージャーに通知します。

注意:インポートされた「デフォルト/グローバル」スレッドプールエグゼキューターで実行されるスリープ1000の実行-スレッドが不足する可能性があります。注意:val commander =送信者は元の送信者への参照を「閉じる」ために必要です、onSuccessが実行されるときの原因-アクター内の現在の送信者はすでに他の「送信者」に設定されている可能性があります...

ログ:

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()

  }
}
15
ya_pulser

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で通話をブロックするための最良の方法は何ですか。ブロックコールを実行するアクターの複数のインスタンスを作成し、その前に router を配置して、アプリケーションの他の部分からは1つのアクターのように見えるようにすることで、スケールアウトすることをお勧めします。

1
theon