Hello Actor
細かい機能の解説はあとの回として、
まずはアクターシステムを使ったプログラミングを体験してみましょう。
Goではアクターシステムが商用でも十分利用できるものは、
下記のProto Actorとergoがあります。
Proto.Actorは、Akka.NET のオリジナル作成者 Roger Johansson によって開発されているもので、
Akka/Pekko と基本の部分がある程度似た形になっています。
あくまで似ている程度で、現在のAkka/Pekkoほど強力なツールが揃っているわけではありませんが、
Proto Actorで概念や作り方を覚えると、少ない労力でAkk/Pekkoにも移行などもができるようになります。
細部は異なりますが、概念などはかなり似ていますのですんなり理解できるはずです。
ergoは Erlang/OTPに影響を受けたアクターベースなフレームワークで、
他パッケージ依存が一切なく、かつErlang/OTPよりも速い!と謳っているものです。
本エントリでは アクターシステムのデファクトなAkka/Pekkoに近いProto Actorを中心に扱っていきます。
はじめてのProto Actor
GoでProto Actorを利用する場合は下記のコマンドで追加するだけです。
任意のGo projectで実行してください。
$ go get github.com/asynkron/protoactor-go/...
# 標準出力に書き込みをサポートしてくれるもの
$ go get github.com/asynkron/goconsole
今回は利用しませんが、いずれかの回で解説するProtocol Buffer Compilerもついでに導入しておくといいでしょう。
Proto Actorでは、物理的に異なるサーバとクラスタを作ることができ、
ノード間の通信でgRPCを利用したスケーラブルなアクターシステム(Proto ActorではVirtual Actorと呼ばれる)が
利用できます。
分散処理・マイクロサービスアーキテクチャなどで利用する場合に便利です。
# macの場合
$ brew install protobuf
Proto Actor自体にはWebアプリケーションで一般的なHTTP周りの機能はありません。
(Webアプリケーションフレームワークではありません)
ですがWebアプリケーションフレームワークなどへの導入は特に意識することなく導入できますので、
安心してください
Akka HTTPのようなパッケージがないだけで、echoなどでも簡単に利用できます。
処理ラインタイムをサポートするものくらいのライトさです。
最初は下記のコードを動かしてみます。
package main import ( "fmt" console "github.com/asynkron/goconsole" "github.com/asynkron/protoactor-go/actor" ) type Hello struct{ Who string } type HelloActor struct{} func (state *HelloActor) Receive(context actor.Context) { switch msg := context.Message().(type) { case Hello: fmt.Printf("Hello %v\n", msg.Who) } } func main() { system := actor.NewActorSystem() props := actor.PropsFromProducer(func() actor.Actor { return &HelloActor{} }) pid := system.Root.Spawn(props) system.Root.Send(pid, Hello{Who: "Roger"}) _ , _ = console.ReadLine() }
actor.NewActorSystem()
はアクターシステム自体を生成するコードです。
いくつかオプションはありますが最初はこれだけで問題ありません。
これが実行されることでアクターシステムが利用できるようになります。
アクターモデルはそれぞれが独立して仕事をし、メッセージを用いて連携する仕組みになっていますが、
アクター間は下記のようにヒエラルキーが存在します。
これらは全て情報伝達の経路、ニューラルネットのようなものだと思ってください。
actor.NewActorSystem()
はこのヒエラルキーを管理するための仕組みだと思っていただけるといいと思います。
続いて actor.PropsFromProducer(func() actor.Actor { return &HelloActor{} })
ですが、
これは生成するアクターの設定だとおもってください。
callbackで指定するようになっていますが、ここでは HelloActor
が指定されています。
このHelloActorは下記の通りです。
type Hello struct{ Who string } type HelloActor struct{} func (state *HelloActor) Receive(context actor.Context) { switch msg := context.Message().(type) { case Hello: fmt.Printf("Hello %v\n", msg.Who) } }
構造体 HelloActorになにやらメソッドがあります。
Proto Actorでは、アクターとして機能させるには Receive(context actor.Context)
を実装しなければいけないことになっています。
これはHelloActor宛のメッセージを受け取った時に何をするか、を提供します。
アクターはさまざまなメッセージを受け取ります。
未知のアクター、アクターシステム自体からのメッセージなどがあり、
どのメッセージを受けたらどうするか、を記述していきます。
Akka/Pekkoでいうとクラシックスタイルになっていますので、switchで処理を記述していきますが
Genericsなどを利用しても構いません。
ここでは Hello型のメッセージが来たら Helloを出力する、と実装しています。
続いて下記の部分です。
pid := system.Root.Spawn(props)
actor.PropsFromProducer
でアクターについての設定を割り当てたら、
system.Root.Spawn(props)
でアクターを生成します。
Spawnという文字の通りアクターが生まれた、と認識するといいかもしれません。
アクター生成時に任意の名前を割り当てることができますが、
ここではシンプルなアクター生成のみを行います。
アクターが生成されると *actor.PID
が返却されます。
これはアクターの識別子になっており、送受信などで利用される重要なものです。
このPID(Akka/Pekkoでいう ActorRef)が各アクターのアドレスを示すものとなっており、
PIDさえわかればローカル・リモート問わずどこにでもメッセージを送ることができます。
最後に下記の部分を見ていきます。
system.Root.Send(pid, Hello{Who: "Roger"})
これまでの説明で分かる通り、
アクターシステム内に生成されたHelloActorへ(ここではトップレベルで生成されているのでRootになる)
Hello{Who: "Roger"}
メッセージを送信しています。
基本的には並行で動作するため、戻り値を利用することはできません(戻り値自体がありません)。
HelloActorへメッセージを送信するのみでそれ以外は何もしていません。
送信されたメッセージはHelloActorのReceiveのcontextに渡されます。
func (state *HelloActor) Receive(context actor.Context) { switch msg := context.Message().(type) { case Hello: fmt.Printf("Hello %v\n", msg.Who) } }
つまり Hello Roger
と出力されるはずです。
*Proto Actorは アクターへのメッセージングは全て並行で行われます。
並行を意識せずに作れるように抽象化されていますので、
あまり意識する必要はありません。
早速動かすと下記のように出力されます。
$ go run app.go 10:07PM INF actor system started lib=Proto.Actor system=bFMya54d7Jik32TQidKjsx id=bFMya54d7Jik32TQidKjsx Hello Roger
期待通り実行されました。
基本はこれだけです。
なんて簡単!
簡単にできることがわかったところで、次回学校生活の一つ 学力テストを実装してみましょう。
HHKB Studioはいいぞ