チュートリアルの翻訳、11回目(最終回)です。
前回までの訳を含めてチュートリアル[日本語訳]にまとめてありますのでごらんください。


多重化

チャネルを使うことで多重化処理のコードをほとんど書くことなく、複数の独立しているゴルーチンに対してデータ配信することができます。
今回のポイントとしては、サーバーへ送信するメッセージ内にあらかじめ返信用チャネルを含めておき、それを使ってクライアントに返信を行います。
実際のクライアント/サーバプログラムは多くのコードからできていますが、ここでは分かりやすいように単純化した例で説明します。
まずはじめにrequest型を定義し、そこに返信に使用するチャネルを埋め込みます。 

09    type request struct {
10        a, b    int;
11        replyc  chan int;
12    }

サーバ側は大した処理は行いません。整数の二項演算をするだけです。
下のコードは演算を実行し、requestに返信します。

14    type binOp func(a, b int) int

16    func run(op binOp, req *request) {
17        reply := op(req.a, req.b);
18        req.replyc <- reply;
19    }

14行目では関数の「型」を定義しています。この関数はパラメータに2つの整数を取り、整数を返します。

server関数は無限ループしています。
リクエストを受け取るたびに処理を行いますが、処理に時間がかかるとループ内でブロックしてしまうため、ゴルーチンを起動して処理を行わせます。 

21    func server(op binOp, service chan *request) {
22        for {
23            req := <-service;
24            go run(op, req);  // don't wait for it
25        }
26    }

おなじみの方法でserverを作成・起動後、それと接続しているチャネルを返します。

28    func startServer(op binOp) chan *request {
29        req := make(chan *request);
30        go server(op, req);
31        return req;
32    }

次は簡単なテストコードです。
「加算処理を行う関数」をfunc文で作成し、それをパラメータにしてsartServerを呼び出し、サーバを起動します。
つづいてN個のリクエストを連続して送信します。
すべてのリクエストを送信し終えてから、応答を確認します。

34    func main() {
35        adder := startServer(func(a, b int) int { return a + b });
36        const N = 100;
37        var reqs [N]request;
38        for i := 0; i < N; i++ {
39            req := &reqs[i];
40            req.a = i;
41            req.b = i + N;
42            req.replyc = make(chan int);
43            adder <- req;
44        }
45        for i := N-1; i >= 0; i-- {   // doesn't matter what order
46            if <-reqs[i].replyc != N + 2*i {
47                fmt.Println("fail at", i);
48            }
49        }
50        fmt.Println("done");
51    }

このプログラムの問題点は、サーバーがちゃんとシャットダウンしないということです。
mainからリターンするときに、いつかのゴルーチンが処理が長引いてしまうため通信をブロックしてしまいます。
これを解決するために、別のチャネルquitをserver関数に渡します。

32    func startServer(op binOp) (service chan *request, quit chan bool) {
33        service = make(chan *request);
34        quit = make(chan bool);
35        go server(op, service, quit);
36        return service, quit;
37    }

server関数にquitチャネルをを渡します。
server関数では受け取ったチャネルを次のようにして使います。

21    func server(op binOp, service chan *request, quit chan bool) {
22        for {
23            select {
24            case req := <-service:
25                go run(op, req);  // don't wait for it
26            case <-quit:
27                return;
28            }
29        }
30    }

server内のselect文は、caseでリストされた複数の通信のうち処理が続行可能な方を選択します。
すべてがブロックされていれば、どれかが続行可能になるまで待機します。どちらも続行可能ならどちらかがランダムに選択されます。

この場合、selectはquitメッセージを受信しリターンするまでの間、requestメッセージを受信し続けます。

あとは、main関数の最後にquitチャネルにデータを送信するよう変更します。 

40        adder, quit := startServer(func(a, b int) int { return a + b });

55        quit <- true;

まだまだGo言語のプログラミングおよび同期処理について語るところはありますが、このチュートリアルは基本の説明だけにとどめておきましょう。