LL脳がscalaの勉強を始めたよ その107
Scalaコップ本の30章のアクターと並行プログラミングの続きをやっていきますよー
今回は前回やった全体設計を元に大規模なサンプルとしての並列離散イベントシミュレーションの実装に入っていきますね(`・ω・´)
大規模なサンプル:並列離散イベントシミュレーション(続き)
18章で取り扱った離散イベントシミュレーションのアクター版の実装を進めていきますよー
ちなみに前回定義した部品はこんな感じでした
import scala.actors._ import scala.actors.Actor._ // クロック→各オブジェクトに送信 case class Ping(time:Int) // 各オブジェクト→クロックに送信 case class Pong(time:Int, from:Actor) // 開始、終了用のオブジェクトを定義します case object Start case object Stop // 作業項目と作業項目追加用オブジェクトです case class WorkItem(time:Int, msg:Any, target:Actor) case class AfterDelay(delay:Int, msg:Any, target:Actor)
シミュレーションフレームワークの実装
まずはシミュレーションフレームワークのコアになるClockクラスとSimulantトレイトの実装をしていきますよ(`・ω・´)
まずはClockクラスですが、こんな感じになるみたいですね
// アクターとしてClockクラスを実装してやります class Clock extends Actor { //// まずは各フィールドを定義 & 初期化します // シミュレーション状態です private var running = false // シミュレーション上のタイムチックです private var currentTime = 0 // 作業項目一覧 private var agenda:List[WorkItem] = List() // シミュレートする部品オブジェクト一覧デス // 反復処理しかしないのでリストで定義します private var allSimulants:List[Actor] = List() // 処理実行中の部品オブジェクト一覧です // 削除順序が予測できないので集合で定義します private var busySimulants:Set[Actor] = Set.empty // クロック自体は作成してしまえば // 仮に実行してもStartメッセージを受け取るまでは // 何もしないのでとりあえず実行します start() // 新しい部品オブジェクトをクロックに追加するメソッド def add(sim:Simulant){ allSimulants = sim :: allSimulants } // クロックアクターのメインループです def act(){ loop { // シミュレーション実行中でかつ処理中の部品オブジェクトがなければ // タイムチックを先に進めます if(running && busySimulants.isEmpty) advance() // メッセージに対する応答処理です reactToOneMessage() } } // タイムチックを先に進める処理を定義します def advance() { // 作業項目がからの場合でシミュレーションが実行中の場合 if(agenda.isEmpty && currentTime > 0){ println("** Agenda empty. Clock exiting at time " + currentTime + ".") // シミュレーションを停止します self ! Stop return } // タイムチックを先に進めますね currentTime += 1 println("Advancing to time" + currentTime) // 現在のタイムチックに割り当てられた作業項目を全て実行します processCurrentEvents() // 全ての部品オブジェクトにPingを送信します for(sim <- allSimulants) sim ! Ping(currentTime) // 全ての部品オブジェクトを処理中として設定します // Pong返答の内容で解除していきますデス(`・ω・´) busySimulants = Set.empty ++ allSimulants } // advanceで呼び出した現在のタイムチックの作業項目実行処理です private def processCurrentEvents(){ // 作業項目一覧内で処理対象になる項目をピックアップします val todoNow = agenda.takeWhile(_.time <= currentTime) // ピックアップした項目を削除します // 作業項目は時系列なので先頭からlengthで処理しますです agenda = agenda.drop(todoNow.length) // 各作業項目を処理していきます for(WorkItem(time, msg, target) <- todoNow){ // 処理スケジュールロジックが狂っていないことをチェックします // 具体的にはタイムチックがずれていたらErrorとします assert(time == currentTime) // 各部品オブジェクトに処理を行うようメッセージを送信します target ! msg } } // Clockが受信するメッセージを処理します def reactToOneMessage(){ react { // 作業項目を追加する旨のメッセージです case AfterDelay(delay, msg, target) => // 送られてきたメッセージを元に作業項目をリストに追加します val item = WorkItem(currentTime + delay, msg, target) agenda = insert(agenda, item) // 部品オブジェクトからの処理終了メッセージ case Pong(time, sim) => // ロジックの狂いをチェックします assert(time == currentTime) assert(busySimulants contains sim) // 処理中オブジェクト一覧から対象を取り除きます busySimulants -= sim // シミュレーションを開始します case Start => running = true // シミュレーションを終了します case Stop => // 全てのオブジェクトにシミュレーション終了のメッセージを送ります for (sim <- allSimulants) sim ! Stop exit() } } // 18章でやったinsertメソッドをそのまま使いまわします // 作業予定表への作業項目の追加 private def insert(ag:List[WorkItem], item:WorkItem):List[WorkItem] = { if(ag.isEmpty || item.time < ag.head.time) item :: ag else ag.head :: insert(ag.tail, item) } }
とりあえずClockクラスはこんな感じの実装になりますねー、次にSimulantトレイトの実装を次のように定義してやりますです
// 部品オブジェクト用のトレイトを定義しますデス trait Simulant extends Actor { // 抽象メンバーを定義します val clock:Clock def handleSimMessage(msg:Any) def simStarting(){ } // メッセージの受信処理です def act(){ loop{ react{ // Stopメッセージを受けると終了します case Stop => exit() // Pingを受け取った場合の振る舞いdす case Ping(time) => // timeが1の場合(スタート時)はシミュレーションを開始します // サブクラスのsimStartingでシミュレーション内容を定義します if(time == 1) simStarting() // Clockアクターに返答を返します clock ! Pong(time, self) // 他のメッセージについてはサブクラスの // handleSimMessageで処理します case msg => handleSimMessage(msg) } } } // 部品オブジェクト作成と同時に実行してやります start() }
Simulantトレイトの実装も以上になりますね。これらのフレームワークを元に実際の回路の実装に入っていくことになります