2009-11-16

Go サンプルコードを読んでみる: A multiplexed server

Rob Pile が Go の channel の説明で使っていたサンプルを読んでみよう。 以下のコードはどれも、Rob Pike のスライドよりコピペしたもの。

まずはサーバのコード(スライドの 30 枚目)。

[A multiplexed Server]
 1: type Request struct {
 2:     a, b    int;
 3:     replyc  chan int; // reply channel inside the Request
 4: }
 5:
 6: type binOp func(a, b int) int
 7: func run(op binOp, req *request) {
 8:     req.replyc <- op(req.a, req.b)
 9: }
10:
11: func server(op binOp, service chan *request) {
12:     for {
13:         req := <-service; // requests arrive here
14:         go run(op, req);  // don't wait for op
15:     }
16: }
17:
18: func StartServer(op binOp) chan *request {
19:     reqChan := make(chan *request);
20:     go server(op, reqChan);
21:     return reqChan;
22: }

Go の文法はよくわからないが、このコードが何をするものなのかは、なんとなくわかる。

1 〜 4 行目は構造体の定義。replyc が整数の通る channel。その名前から、サーバへのリクエストに対する結果を返すものだと想像がつく。

6 〜 9 行目; サーバにおける処理の定義。2 つの整数に対して、2項演算を適用して、その結果を replyc に送る。

11 〜 16 行目; サーバ本体。引数は処理すべき 2 項演算とリクエストが通ってくる channel。for は無限ループ。13 行目でクライアントからのリクエストを取り出して、14 行目で実行。ところで、この 14 行目にある go が goroutine と呼ばれるスレッドを起動するものらしい。run はすぐ上の 7 行目で定義されている関数。それを別スレッドで処理するってことだ。

18 〜 22 行目が、サーバを起動する関数。引数としてサーバが処理する演算を受け取る。実行する内容は、リクエストを受け付ける channel を用意し(19 行目)、サーバ本体を別スレッドで起動し(20 行目)、リクエストを流す channel をクライアントに返す(21行目)。

次はクライアント(スライドの 31 枚目)。

[The client]
23: // Start server; receive a channel on which
24: // to send requests.
25: server := StartServer(
26:           func(a, b int) int {return a+b});
27: // Create requests
28: req1 := &Request{23,45, make(chan int)};
29: req2 := &Request{-17,1<<4, make(chan int)};
30: // Send them in arbitrary order
31: server <- req1; server <- req2;
32:
33: // Wait for the answers in arbitrary order
34: fmt.Printf("Answer2: %d\n", <-req2.replyc);
35: fmt.Printf("Answer1: %d\n", <-req1.replyc);

こちらの方は何をしているかがコメントに書いてある。リクエストごとに結果を流す channel が設けられているおり(Request構造体)、またリクエストごとに処理スレッドが起動されるので(14 行目)、結果を受け取る順番はリクエストの順番と関係ない。

続くスライド(32枚目)では、selectというステートメントが説明されている。サンプルではこれを使って server 関数(サーバ本体)に停止機能を付けている。

(server)
36: func server(op binOp, service chan *request, quit chan bool) {
37:     for {
38:         select {
39:         case req := <-service:
40:             go run(op, req); // don't wait
41:         case <-quit:
42:             return;
43:         }
44:     }
45: }

停止要求を伝えるための channel を用意し(quit)、ループ内で処理リクエスト(service channel)と停止要求とを二重待ちしている。スレッドの停止を実装するとゴチャゴチャしがちなんだけど、channel と select のおかげでずいぶんすっきりとしたコードになっている。

せっかくだから、これらと同じ機能を持つサーバとクライアントを Ruby で Queue を使って書くとどうなるか、また、Objective-C (Cocoa) だとどうなるのか、なんてことを考えてみたい。少し調べてみたところ、Ruby の場合は Go とほとんど同じように書けそう。一方、Objective-C の方は、Go や Ruby にくらべて面倒なことになりそう(コード量が何倍にもなる)。

関連リンク

関連記事

0 件のコメント:

コメントを投稿