1

初期化コストとメモリ フットプリントが大きいオブジェクトがあります。初期化時間は人が認識できますが、作成頻度は低くなります。

class HeavyClass {
  heavyInit()
}

私の解決策は、事前に作成された単一のオブジェクトを持つ Provider アクターを作成し、要求に応じて即座に提供することです。その後、プロバイダーは次のオブジェクトの作成に進みます。

class HeavyClassProvider extends Actor {

  var hc: Option[HeavyClass] = Some(new HeavyClass())

  override def receive = {
    case "REQUEST" =>
      sender ! { hc getOrElse new HeavyClass() }
      self ! "RESPAWN"
      hc = None

    case "RESPAWN" if (hc == None) => hc = Some(new HeavyClass())
  }

}

そして消費者:

abstract class HeavyClassConsumer extends Actor {

  import context.dispatcher

  import akka.pattern.ask
  import scala.concurrent.duration._
  import akka.util.Timeout

  implicit val timeout = Timeout(5, SECONDS)

  var provider: ActorRef
  var hc: Option[HeavyClass] = None

  override def receive = {
    case "START" =>
      ((provider ask "REQUEST").mapTo[HeavyClass]
       onSuccess { case h: HeavyClass => hc = Some(h) })
  }

}

これはよくあるパターンですか?コードは奇抜に感じます。これを行う明確なクリーンな方法はありますか?

4

2 に答える 2

1

ある種の同期化されたファクトリで行われることが多いと思いますが、特に呼び出し元のコードが非同期パターンで構築されている場合、アクターは他の同期メカニズムと同じように優れているようです。

HeavyClass現在の実装の潜在的な問題の 1 つは、 「一度に」要求された複数のオブジェクトの作成を並列化できないことです。これは機能であり、いくつかを並行して作成するとシステムが停止する可能性があります。一方、「単に遅い」場合は、「オンデマンド」インスタンスの作成を独自のスレッド/アクターにスピンオフすることをお勧めします。

于 2014-07-27T02:39:21.807 に答える
1

ソリューションの問題は、アクターを呼び出すとnew HeavyClass()、その計算が処理されるまでブロックされることです。Future または別の Actor でそれを行うと、それを回避できます。これを行う1つの方法は次のとおりです。

import akka.pattern.pipe
...

class HeavyClassProvider extends Actor {

  // start off async computation during init:
  var hc: Future[HeavyClass] = Future(new HeavyClass)

  override def receive = {
    case "REQUEST" =>
      // send result to requester when it's complete or
      // immediately if its already complete:
      hc pipeTo sender
      // start a new computation and send to self:
      Future(new HeavyClass) pipeTo self
    case result: HeavyClass => // new result is ready
      hc = Future.successful(result) // update with newly computed result
    case Status.Failure(f) => // computation failed
      hc = Future.failed[HeavyClass](f)
      // maybe request a recomputation again
  }
}

(私はそれをコンパイルしませんでした)

私の最初のソリューションの特徴の 1 つは、同時に計算される Future の数を制限しないことです。複数のリクエストを受け取った場合、複数の先物が計算されますが、このアクタには競合状態はありません。これを制限するには、アクタに Boolean フラグを導入して、既に何かを計算しているかどうかを通知します。また、これらすべてvarの は動作に置き換えることができますbecome/unbecome

複数のリクエストが与えられた単一の同時 Future 計算の例:

import akka.pattern.pipe
...

class HeavyClassProvider extends Actor {

  // start off async computation during init:
  var hc: Future[HeavyClass] = Future(new HeavyClass) pipeTo self
  var computing: Boolean = true

  override def receive = {
    case "REQUEST" =>
      // send result to requester when it's complete or
      // immediately if its already complete:
      hc pipeTo sender
      // start a new computation and send to self:
      if(! computing)
        Future(new HeavyClass) pipeTo self
    case result: HeavyClass => // new result is ready
      hc = Future.successful(result) // update with newly computed result
      computing = false
    case Status.Failure(f) => // computation failed
      hc = Future.failed[HeavyClass](f)
      computing = false
      // maybe request a recomputation again
  }
}

編集:コメントで要件についてさらに議論した後、ここでは、各リクエストで新しいオブジェクトを非ブロッキング方式で送信者/クライアントに送信する別の実装を示します。

import akka.pattern.pipe
...

class HeavyClassProvider extends Actor {
  override def receive = {
    case "REQUEST" =>
      Future(new HeavyClass) pipeTo sender
  }
}

そして、次のように単純化できます。

object SomeFactoryObject {
  def computeLongOp: Future[HeavyClass] = Future(new HeavyClass)
}

この場合、アクターは必要ありません。このような場合に同期メカニズムおよびノンブロッキング計算としてアクタを使用する目的は、そのアクタが結果をキャッシュし、単なる よりも複雑なロジックで非同期計算を提供することです。Futureそれ以外の場合Futureは十分です。

于 2014-07-27T04:09:03.827 に答える