Geeks)企業システムの企画・開発に携わる技術者集団のブログです。開発言語やフレームワークなどアプリケーション開発に関する各種情報を発信しています。ウルシステムズのエンジニア有志が運営しています。この広告は、90日以上更新していないブログに表示しています。アクターモデルの概要について説明した。今回は、2つのアクター間でメッセージを交換する基本的なプログラムを紹介する。同等のプログラムを作成したものを掲載する予定だ。全てのソースコードは、githubリポジトリにある。全体の処理の流れ今回のサンプルでは、親と子の2つのアクターを作成し、以下のように動作させることを目的としている。親アクターが子アクターにメッセージとして整数値を送信する。子アクターは親アクターから整数値を受け取ると、その値の2乗した値を親に送り返す。親アクター(MyApp)と子アクター(SubApp)は、それぞれが独立したタスクを実行するオブジェクトである。親アクターが、クライアント(ApplicationMain)からRequestメッセージを受け付けると、子アクターを生成して処理を行う。親アクターと子アクターはRequestとResponseのメッセージを介して通信を行う。このサンプルでは、クライアントから親アクター、親アクターから子アクターへのメッセージはどちらも同じRequestオブジェクトを使っている。今回のサンプルの全体のイメージをクラス図で示す。クラスやメソッドなどで、サンプルで解説しない部分については割愛してある。子アクターの定義まずは、子アクターの実装から見ていこう。検証のため、時間の出力とアクターを実行しているスレッドを待機する。メッセージを送信したアクター(親アクター)を指す。Responseという2つのケースクラスを定義して、SubActorのコンパニオンオブジェクトのフィールドに持たせた。2つのケースクラスはそれぞれ整数値のフィールドを1つ持っている。子アクタークラスの定義である。アクタークラスを定義するには、Actorトレイトを継承すればよい。receiveメソッドの定義メッセージを受信した際の処理は、receiveメソッドに定義する。ここでは、親アクターからRequestメッセージを受け取った場合に、Responseメッセージを返すという内容を定義している。receiveメソッドは、Any型の引数をとり、Unit型の値を返す部分関数(PartialFunction)である。receiveメソッドでは、パターンマッチを使って、receiveメソッドの引数、つまりメッセージオブジェクトがRequest型かどうかを判定し、該当する場合には、Request型のオブジェクトのフィールドnumの値を変数nに設定している。デフォルトのメッセージ処理を適用し、それでも処理できないメッセージは未処理メッセージとして記録する。caseの後の処理ブロックでは、検証のためコンソールに時刻を出力した後、アクターを実行しているスレッドを待機する処理を入れている。スレッドを停止する処理はアプリケーション全体のパフォーマンス劣化を招くため本来は書いてはいけない。時間のかかる処理をどうやって定義するかは次回以降に解説する。その後、sender()でメッセージを送信したActorRef(ここでは親アクターの参照)を取得し、メッセージResponseオブジェクトを生成し、nの2乗値を設定して、それをメッセージとして親アクターに送り返している。以上が子アクターの実装である。続いて、親アクターの定義を見ていく。10回以上、応答を受けたら、停止するようにする。親アクターでは、状態を管理するためにfinishedフィールドを定義している。アクターにはフィールドを定義可能で、receiveメソッド内であればフィールドの値を更新しても問題ない。マルチスレッドで実行されるオブジェクトのフィールドを更新する際は、synchronizedなどの排他制御が必要となる。調整するため、排他制御の考慮は不要である。複数種類のメッセージの取り扱い親アクターでは、caseを2つ記述している。複数種類のメッセージを取り扱うには、caseを複数並べるようにする。Request型のメッセージを受け取った際の処理である。子アクターの場合と異なり、メッセージオブジェクトのフィールドを見る必要が無いため、メッセージオブジェクト全体をreqという変数に格納している。ここでは、リクエストを受けるたびに、子アクターを生成してメッセージを送信している。子アクターの生成を行っている。contextはActorトレイトが持っているプロパティで、アクターの情報を保持するフィールドである。使う約束になっている。Propsはアクター生成時に設定する初期値などを保持するオブジェクトである。詳細については、次回以降の記事で解説する。変数の型宣言が省略できるため気づきづらいが、actorOfの戻り値は、Actorではなく、ActorRefというアクターインスタンスへの参照を保持するオブジェクトになっている。ActorRefを介した仕組みにすることで耐障害性や位置透過性などの考慮をしている。詳細については、次回以降の記事で解説する。2つめのcaseは、子アクターからResponseメッセージを受け取った際の処理である。処理結果をコンソールに出力した後、子アクターにPoisonPillという組み込みの停止用メッセージを送り、子アクターを停止している。Akkaシステムの終了その後、子アクターが処理を終えた回数をフィールドfinishedに設定し、10回以上処理が終わったら、アクター全体をcontext.system.terminate()で終了するようにしている。全体の設定などを管理するオブジェクトである。以上が親アクターの定義である。処理は全て非同期で行われるため、!メソッドでメッセージを送信すると送信結果を待たずに、次の処理が行われる。そこで、子アクターが送信結果を親アクターに送り返すことで、同期処理のように処理結果を受け取ることができる。パターンと呼んでいる。アプリケーションを開始するmainメソッドを紹介する。1から10までのリクエストを親アクターに送信する。呼ばれるまで待機する。使うには、ActorSystemを作成する必要がある。上記は最も簡単な作成方法である。全体の設定を保持し、全てのアクターインスタンスを管理し、アクターを動作させるためのスレッドープールを保持する。重量級のオブジェクトであるため通常はアプリケーションで1つだけ作成する。ActorSystemから、アクターを生成してメッセージを送信し、処理を開始する。ここでは親アクターを1つ生成して、1から10までのRequestを送っている。その後、ActorSystemが終了するまで待機している。環境により、順序は異なる可能性があるが、実行すると以下のような結果が得られる。49子アクターが10件生成され、ほぼ同時にメッセージ処理が起動し、1秒後にその結果をほぼ同時に順不同で返している。スレッドプールで並列に実行されていることがわかるだろう。順序を固定にしたい場合は、子アクターの生成を一度だけ行い、親アクターのフィールドに設定することで実現できる。これは1つのアクターに対しては、到達したメッセージの順序で処理を行うことが保証されているためだ。その場合子アクターの処理は並列にならないため、処理時間がかかることに注意が必要となる。まとめアクターモデルを使うことで、マルチスレッドプログラミングにありがちな、状態更新時のロックや、スレッド間の協調といった煩雑な処理を意識することなく、非同期処理を書くことができる。書くとどうなるかについて解説する。設定や、障害からの復帰、ルーター、テスト、クラスタリングなどを扱っていく。アクターモデルとAkkaの概要XP祭り2016でエンタープライズへのアジャイル開発の導入事例を講演Cloud9の概要プロフィールid:enterprisegeeks企業システムの企画・開発に携わる技術者集団です。開発言語やフレームワークなどアプリケーション開発に関する各種情報を発信しています。ウルシステムズのエンジニア有志が運営しています。月別アーカイブはてなブログをはじめよう!enterprisegeeksさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。