Golang Cafe #9 まとめ Go言語でデザインパターン
2013/12/22に開催された「Golang Cafe #9」についてのまとめです。
本来は参加する予定ではなかったのですが、時間がとれたので参加しました。
今回は
増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/03/21
- メディア: 大型本
- 購入: 15人 クリック: 287回
- この商品を含むブログ (206件) を見る
サンプルコードは+TakashiYokoyama氏が用意してくれました。
時間にも限りがあり、singlethreadexecution、immutable、およびguardedsuspensionの3つのみの検証となりました。
singlethreadexecution
複数スレッドから同じ値を参照すると整合性がとれなくなってしまうというもの。
メイン関数にruntime.GOMAXPROCS(2)を追加して実行すると下記のように表示されます。
(私のノートPCで実行すると下記のように出力されるのですが、私のデスクトップやMacチームは下記のような結果にはなりませんでした)
$ go run main.go Testing Gate, hit CTRL+C to exit. Alice BEGIN Bobby BEGIN Chris BEGIN ***** BROKEN ***** No.2157: Chris, Canada ***** BROKEN ***** No.2249: Bobby, Brazil ***** BROKEN ***** No.8642: Bobby, Brazil ***** BROKEN ***** No.10887: Chris, Canada ***** BROKEN ***** No.11187: Bobby, Brazil ***** BROKEN ***** No.13887: Chris, Canada ***** BROKEN ***** No.15086: Alice, Alaska ***** BROKEN ***** No.17209: Chris, Canada
これを防ぐにはどうしたらいいのか、参加者それぞれ方法がありましたが、私が一番スマートだと思った方法はこれです。
Gateにflagを追加します。
type Gate struct { counter int name string address string flag chan bool }
Start関数のforループ内を修正します。
func(thread UserThread) Start() { println(fmt.Sprintf("%s BEGIN", thread.myName)) for { <-thread.gate.flag thread.gate.Pass(thread.myName, thread.myAddress) time.Sleep(1 * time.Millisecond) thread.gate.flag <- true } }
main関数のgate初期化のあとにflagを初期化します。
func main() { runtime.GOMAXPROCS(2) println("Testing Gate, hit CTRL+C to exit.") gate := Gate {counter: 0, name: "Nobody", address: "Nowhere"} gate.flag = make(chan bool, 1) gate.flag <- true // 以下は省略 }
Gate自体にバッファ1のチャンネル変数を追加することによりGateへの同時アクセスを制御しようというものです。
MutexのLock、Unlockを使用しても制御できそうですがこの方がスマートに思えました。
これで実行すると、下記のように正常に表示されます。
$ go run main.go Testing Gate, hit CTRL+C to exit. Alice BEGIN Bobby BEGIN Chris BEGIN
チャンネル変数はgoroutine間の値の受け渡しに使うだけでなく、バッファを持たせることでロックの代わりに使用できる典型的な例です。
immutable
実行結果の一例ですが下記のような内容続きます。
$ go run main.go 3 prints [ Person: name = Alice, address = Alaska ]1 prints [ Person: name = Alice, address = Alaska ]2 prints [ Person: name = Alice, address = Alaska ] 3 prints [ Person: name = Alice, address = Alaska ] 1 prints [ Person: name = Alice, address = Alaska ]2 prints [ Person: name = Alice, address = Alaska ]3 prints [ Person: name = Alice, address = Alaska ]
先ほどのサンプルでは一つの値に対して変更できる術があったがために不整合が起きてしまいましたが、このサンプルのように変更する方法がない場合には当然不整合は起きないというものです。
guardedsuspension
まず実行結果ですが以下のようになります。
$ go run main.go ClientThread requests [ Request No. 0 ] ServerThread handles [ Request No. 0 ] ClientThread requests [ Request No. 1 ] getRequest wait ServerThread handles [ Request No. 1 ] getRequest waitClientThread requests [ Request No. 2 ] ServerThread handles [ Request No. 2 ] getRequest waitClientThread requests [ Request No. 3 ] ServerThread handles [ Request No. 3 ] getRequest waitClientThread requests [ Request No. 4 ] ServerThread handles [ Request No. 4 ] getRequest waitClientThread requests [ Request No. 5 ] ServerThread handles [ Request No. 5 ]
Clientのrequestに対してServerのhandle順次発生しています。
ポイントとなるのは以下の2つのメソッドです。
func (queue RequestQueue) getRequest() Request { queue.cond.L.Lock() if queue.queue.Len() <= 0 { // wait println("getRequest wait") queue.cond.Wait() } element := queue.queue.Front() request := element.Value.(Request) queue.queue.Remove(element) queue.cond.L.Unlock() return request }
func (queue RequestQueue) putRequest(req Request) {
queue.cond.L.Lock()
queue.queue.PushBack(req)
queue.cond.Signal()
queue.cond.L.Unlock()
}
ClientがputRequestで値を設定したあとSignalメソッドを呼びます、するとgetRequestのWaitメソッドで待機していたServerの処理が再開されるというものです。
C#でいうとManualResetEventやAutoResetEventのような使い方ですが、Waitメソッド、Signalメソッドを呼ぶ前後にLock、Unlockが必要なところに気をつけなくてはなりません。
簡単にまとめ
数ヶ月ほどGo言語に取り組んできましたが、やはり「goroutineとチャンネルをどう駆使するか」というところがポイントのように思えてきました。
まだまだ奥が深そうです。
年末と理由にしてはいけませんがまとめが遅くなってしまいました。