GParsのActorでアクターモデルに入門する

はじめに

この記事は、G*Advent Calendar(Groovy,Grails,Gradle,Spock…) Advent Calendar 2016の10日目のエントリです。

9日目 > nobeansさんのGrailsでUnix/Linux的実行可能WARファイルをつくるです。

11日目 > tyamaさんのGrailsでServer Sent Eventsを送信!(意訳版)です。

GParsとは

GParsは、GroovyとJavaで利用できる並行・並列処理を利用しやすくなるライブラリです。

[Groovy]GParsで並列処理(基本&コレクション編)を見ていただくと、 旧来のThreadや、java.util.concurrentをそのまま使うよりも並行処理が書きやすそうだと感じたのではないでしょうか。

GroovyがApacheに寄贈され、codehausが停止してしまったため、ソースコードやドキュメントがgithubに移管されています。

GPars/GPars

versionは、1.2.1のままです。

ですので、Gradleで利用する際は、以下のように依存を追加すれば利用できるようになります。

// https://mvnrepository.com/artifact/org.codehaus.gpars/gpars
compile group: 'org.codehaus.gpars', name: 'gpars', version: '1.2.1'

Actorモデルについて

アクターモデルについては、アクターモデルを見てください。

アクターモデルの実装されているもので代表的なものは、ErlangAkkaが挙げられます。

この2つについては、もうエコシステムとも呼べるようなものになってしまっているので、アクターモデルだけを知ろうとすると、どこから手をつけていいのか分かりにくくなります。

そこで、GParsのActorを使ってアクターモデルの考え方を理解して行こうと思います。

GParsのActor

GParsのActorのドキュメントを見ると、Actorの作り方はとてもシンプルです。

import groovyx.gpars.actor.Actor

def actor = Actors.actor {
    loop {
        react { msg ->
            println "Received: $msg"
        }
    }
}

actor.send 'Hello, GPars!'

actor.join() // 停止するまで立ち上がったままにする。

// 止めるときはCtrl-C or プロセスを落とす

いわゆるPingPongをする場合はこんな感じです。

@Immutable
class PingMessage {
    String message
}

@Immutable
class PongMessage {
    String message
}

@Log
class PongActor extends DefaultActor {
    @Override
    protected void act() {
        loop {
            react { msg ->
                log.info "Pong Received: $msg"
                switch (msg) {
                    case PingMessage:
                        reply new PongMessage(message: msg.message)
                }
            }
        }
    }
}

@Log
class Main {
    static void main(args) {
        log.info("start")

        def pingActor = Actors.actor {
            loop {
                react { msg ->
                    log.info "Ping Received: $msg"
                    switch (msg) {
                        case String:
                            reply new PingMessage(message: msg)
                            break
                        case PongMessage:
                            terminate() // actorを停止させる
                            break
                    }
                }
            }
        }

        def pongActor = new PongActor().start()

        pingActor.send 'Hello, GPars!', pongActor

        // 1秒だけ待つ
        [pingActor, pongActor]*.join(1, TimeUnit.SECONDS)

        log.info("end")
    }
}

ちょっとAkkaのActorに似せるように書いて見ましたが、いかがでしょうか。

Groovyはswitch文のcaseにクラスを指定することも出来るので、Scalaのパターンマッチっぽく見えます。

また、@Immutableを使うことで、 GroovyのAST変換用いてcase classっぽいクラスを作ることも出来ます。

Akkaでは、ActorSystemと呼ばれるSupervisorを使用して、 アクターの階層構造を構築していきますが、GParsのActorにはありません。

GParsの資料を色々探していたところ、 6.2. Supervisingを見つけました。

以下のコードを見る限り、頑張ればやれそうです。

こうして見ると、AkkaのActorSystemは、かなり成熟しているので、理解することで巨人の肩に乗ることが出来ると思います。

終わりに

駆け足でGParsのActorを紹介して見ましたが、いかがでしょうか。

同じJVMで動くAkkaに比べて機能としては少ないかもしれませんが、ここで紹介したDefaultActor以外にも、 より柔軟に条件が書けるDynamicDispatchActorや、Actorsヘルパークラスを使って簡単にactorを作れたり等、 Actorモデルを理解するには十分な機能が揃っています。

GPars自体は、Actorだけでなく、他にもSTMやCSPなど並行・並列処理に向いた機能を提供しています。

Jetty等のコンテナを利用していると意識しなくても並行処理が利用できてしまいますが、 並行・並列処理をより知りたくなった際には、GParsを使って理論の理解に役立ててください。