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 にくらべて面倒なことになりそう(コード量が何倍にもなる)。
関連リンク
- The Go Programming Language (公式サイト)
0 件のコメント:
コメントを投稿