Groovy でのマルチスレッド・プログラミングを練習するために、『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にあるマルチスレッド・プログラミングのデザインパターンを Groovy/GPars で書いてみます。
今回は「Single Thread Execution」パターンです。 Critical Section, Critical Region とも呼ばれるそうです。 このパターンでは、複数のスレッドから変更される可能性のあるフィールドやリソースがある場合、その変更は同時に1つのスレッドからしか行えないようにする、というパターンです。 Single Thread Execution パターンはマルチスレッド・プログラミングの基本となるものだそうなので、いろいろな方法でイジってみます。
サンプル・コード
『増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編』にあるコードを Groovy で書き換えたものをサンプルに使わせてもらいます(ちょっと変更ありますが)。 まずは実行するスクリプト:
println 'Testing Gate, hit CTRL+C to exit.' Gate gate = new Gate(); // 各旅人を別スレッドで実行 Thread.start passenger(gate, "Alice", "Alaska") Thread.start passenger(gate, "Bobby", "Brazil") Thread.start passenger(gate, "Chris", "Canada") // 旅人(Closure オブジェクト)を返す def passenger(Gate gate, String name, String address){ return { println "$name BEGIN" while(true){ gate.pass(name, address) // 門を通る } } }
1つの門 (gate) があり(動作は後述)、3人の旅人 (passenger) がその門を独立したスレッドでひたすら通り続けます。 門の実装は
class Gate { private int counter = 0; private String name = 'Nobody' private String address = 'Nowhere' void pass(String name, String address){ this.counter++; this.name = name this.address = address check(); } private void check(){ if(name.charAt(0) != address.charAt(0)){ // 名前と住所の頭文字が一致してなければ「BROKEN」 println "***** BROKEN ***** No. $counter: $name, $address" } } }
となっていて、通るたび(pass() が呼び出されるたび)名前と住所を記録してチェックします。 チェック時に名前と住所の頭文字が異なっていれば「BROKEN」と表示します。 シングル・スレッドで pass() が呼ばれれば問題ありませんが、マルチスレッドで、あるスレッドが pass() メソッドを呼び出しているときに別のスレッドが pass() の処理に入ると問題が起こります。
synchronized でスレッドセーフにする
スレッドセーフにする一番簡単な方法は Gate クラスの pass() メソッドを synchronized にするだけです:
class Gate { private int counter = 0; private String name = "Nobody"; private String address = "Nowhere"; // synchronized にする public synchronized void pass(String name, String address){ this.counter++; this.name = name; this.address = address; check(); } private void check(){ if(name.charAt(0) != address.charAt(0)){ println "***** BROKEN ***** No. $counter: $name, $address" } } }
Actor でスレッドセーフにする
上記の方法で手軽にスレッドセーフにできるんですが、せっかく Groovy でやってるので GPars なんかも使ってやりたいところ。 ってことで、GPars での解決法も考えてみます。
GPars で Single Thread Execution パターンに使えそうなのは Actor と Agent でしょうかね。 さらに Actor ではいくつかやり方があるようです。 今回はあえて一番手間がかかりそうな、独自の Actor クラスを作成する方法を見ていきます。 その他のものはそのうちに。
独自の Actor クラスを作成する場合、DefaultActor を継承して作成するのが通例のようです。 実装が必要なメソッドは act() メソッドです:
import groovyx.gpars.actor.DefaultActor class GateActor extends DefaultActor{ private int counter = 0; private String name = "Nobody"; private String address = "Nowhere"; @Override void act() { loop { react { args -> // メッセージの受信 pass(args[0], args[1]) } } } private void pass(String name, String address){ this.counter++; this.name = name; this.address = address; check(); } private void check(){ if(name.charAt(0) != address.charAt(0)){ println "***** BROKEN ***** No. $counter: $name, $address" } } }
- act() メソッド内の loop(), react() メソッドの呼び出しは大抵そのようにするようです。 react() メソッドに渡されているクロージャは、この Actor がメッセージを受信した際に呼び出されるようです。
- pass() メソッド、check() メソッドは元のサンプルの Gate メソッドに定義されていたものと(ほぼ)同じです。 特に pass() メソッドは synchronized にする必要はありません。
この GateActor を使って、実行するスクリプトは以下のようになります:
println 'Testing Gate, hit CTRL+C to exit.' GateActor gate = new GateActor().start() // Actor もスレッドみたく start する Thread.start passenger(gate, 'Alice', 'Alaska') Thread.start passenger(gate, 'Bobby', 'Brazil') Thread.start passenger(gate, 'Chris', 'Canada') def passenger(GateActor gate, String name, String address){ return { println "$name BEGIN" while(true){ gate << [name, address] // Actor へメッセージを送信 } } }
- Actor オブジェクトはスレッドのように start() メソッドを呼び出す必要があります。
- Actor オブジェクトに対する処理は、メソッド呼び出しではなくメッセージ送信となるので、<< 演算子やsend() メソッドなどを使用します。
独自の Actor クラスを作成するとそこそこコードの分量が増えますが、実際にはもっと簡単に Actor オブジェクトを作成できます。 これは次回に。
参考 URL
増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/03/21
- メディア: 大型本
- 購入: 15人 クリック: 287回
- この商品を含むブログ (199件) を見る