倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Groovy で Active Object パターン

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』に載っているデザインパターンを Groovy/GPars で書こうシリーズ、今回は Active Object パターン。 これは別名 Actor パターンとも呼ばれるので、GPars の Actor を使っての実装を試みましょう。

ところで、『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』には Active Object を「能動的なオブジェクト」と呼んでますが、メッセージを受け取って処理を行うってことを考えると、どちらかと言えば受動的な感じがするんですけど・・・

サンプル・コード


今回は、作成するソースは実行スクリプトのみにします。 Actor は Actors#actor によって作成します。 また、パターンの要の ActiveObject はもちろん Actor として作成しますが、その Actor にメッセージを送信して処理を依頼するクライアントも Actor として作成します。 混乱無きよう。 作成する Actor は

  • activeObject : ActiveObject パターンの要となる Actor
  • displayClient : activeObject に文字列のメッセージを送って、ディスプレイ表示を依頼する Actor
  • makeClient : activeObject に整数と文字列のメッセージを送って、文字列生成を依頼する Actor

です。 今回は実行スクリプト(activeObject を含む)のソースから見ていきましょう。

実行スクリプト

実行スクリプトのソース。 Active Object パターンの要となる Actor も生成しています:

import groovyx.gpars.actor.Actor
import groovyx.gpars.actor.Actors

def actors = []
def activeObject = Actors.actor{
    loop{
        react{ args ->
            // 受け取ったメッセージの型で場合分け
            switch(args){
                case String:
                    println "displayString: $args"
                    Thread.sleep 10
                    break

                case List:
                    def (count, s) = args
                    def buffer = []
                    count.times{ buffer << s; Thread.sleep 100 }
                    reply buffer.join('')
                    break

                default:
                    assert false
            }
        }
    }
}
actors << activeObject
actors << makeClient('Alice', activeObject)
actors << makeClient('Bobby', activeObject)
actors << displayClient('Chris', activeObject)

Thread.sleep 3000
actors*.join()

メッセージを受信して処理を行う Actor は loop ノードの中に react ノードを書いて実装します。 GPars の Actor は受け取ったメッセージの種類によって react の処理を切り替える(メソッドを引数のシグニチャで切り替えるように)ことができないようなので、自分でメッセージの型などを解析する必要があります(react の中の switch 文)。

displayClient メソッド

displayClient の Actor を作成する displayClient メソッドは簡単な実装:

def Actor displayClient(String name, activeObject){
    return Actors.actor{
        def i = 0
        loop{
            def s = "$name $i"
            activeObject << s.toString()    // メッセージの送信
            Thread.sleep 200
            i++
        }
    }
}

activeObject (Actor) にメッセージを送信するには send() メソッド<< 演算子を用います。

makeClient メソッド

makeClient の Actor も displayClient とそんなに違いませんが、決定的に違うのはメッセージ送信に対して返り値があるという点です。 返り値を処理するには幾つかの方法があるようですが、ここでは以下の3つを見ていきます:

  • sendAndWait() メソッドで処理が終わるまで待つ
  • sendAndContinue() メソッドで非同期に処理を行う
  • sendAndPromise() メソッドで Promise オブジェクト(java.util.concurrent パッケージの Future 風のオブジェクト)を取得する

まずは sendAndWait() メソッドを使う方法。 一番素直な方法かと。

def Actor makeClient(String name, activeObject){
    return Actors.actor{
        def i = 0
        loop{
            def result = activeObject.sendAndWait([i, name[0]])
            Thread.sleep 10
            println "$name: value = $result"
            i++
        }
    }
}

次は sendAndContinue() メソッドを使った方法。 loop ノード下のみを書いてます:

            def result = null
            activeObject.sendAndContinue([i, name[0]], { result = it })
                // 処理が完了すると第2引数のクロージャが実行される
            Thread.sleep 3000
            println "$name: value = $result"
            i++

sendAndContinue() メソッドは、呼び出したらメッセージに対する処理は非同期に行い、次の処理に移ります。 メッセージに対する非同期処理が完了したら、第2引数に指定したクロージャが実行されます。 上記の例では、ここで変数 result に結果をセットしています。 ちなみに返り値が必要ない場合でも非同期処理としてこのメソッドを使用することができます。

最後は sendAndPromise() メソッドを使った方法。 これは java.util.concurrent パッケージの Future と同じようなインターフェースである Promise 型のオブジェクトを返します。 これは以前の記事で見た DataFlowVariable のような型で、get() メソッドや getVal() メソッドなどで結果を取得することが出来ます(計算が終わるまで待たされます)。

            def result = activeObject.sendAndPromise([i, name[0]])
            Thread.sleep 10
            println "$name: value = $result.val"
            i++

ただし、sendAndPromise() メソッドは GPars 1.0 からサポートされるようで、現在の安定版 0.12 では使えないので注意。

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編


プログラミングGROOVY

プログラミングGROOVY