[Go] Go言語による並行処理 4章メモ1

Go言語による並行処理の第4章をまとめていく。 www.amazon.co.jp

4.1 拘束

並行プロセスを安全にする方法として、

  • データをイミュータブルにする(作成後に変更できないようにする)
  • データを拘束によって保護する

などが挙げられる。 拘束について、

  • アドホック拘束: 規約によって拘束を達成する
  • レキシカル拘束: レキシカルスコープを使って適切なデータと並行処理のプリミティブだけを複数の並行処理プロセスで使えるように公開する

の2つがある。

レキシカル拘束

3章でみた、チャネルの読み書きの必要な権限だけを公開するというのもその1つ

次の例では、printDatadataスライスには直接アクセスできず、引数として渡さねばならない。 また、printDataの中ではdataスライスの一部しか見ることができない。

printData := func(wg *sync.WaitGroup, data []byte) {
    defer wg.Done()

    var buff bytes.Buffer
    for _, b := range data {
        fmt.Fprintf(&buff, "%c", c)
    }
    fmt.Println(buff.String())
}

var wg sync.WaitGroup
wg.Add(2)
data := []byte("golang")
go printData(&wg, data[:3])
go printData(&wg, data[3:])

wg.Wait()

for-selectループ

for-selectループを用いるパターンを挙げる。

  • チャネルから繰り返しの変数を送出する
for _, s := range []string{"a", "b", "c"} {
    select {
    case <- done:
        return
    case stringStream <- s:
    }
}
  • 停止シグナルを待つ無限ループ
for {
    select {
    case <-done:
        return
    default:
    }
    // 割り込みできない処理
}

または、

for {
    select {
    case <-done:
        return
    default:
        // 割り込みできない処理
    }
}

4.3 ゴルーチンリークを避ける

ゴルーチンが終了するのは、

  1. 処理を完了する場合
  2. 回復不可能なエラーで処理が続けられない場合
  3. 停止するよう命令された場合

あり、3のようにゴルーチンを終了するには、doneという読み込み専用チャネルを使用する。 ゴルーチンがゴルーチンを生成したのなら、その生成したゴルーチンを停止できるようにするべきである。

doWork := func(done <-chan interface{}, strings <-chan string) <- chan interface{} {
    terminated := make(chan interface{})
    go func() {
        defer fmt.Println("doWork exited")
        defer close(terminated)
        for {
            select {
            case s := <- strings:
                // 処理
                fmt.Println(s)
            case <- done:
                return
            }
        }
    }()
    return terminated
}

done := make(chan interface{})
terminated := doWork(done, nil)

go func() {
    // 1秒後に操作をキャンセル
    time.Sleep(1 * time.Second)
    fmt.Println("Canceling doWork goroutine...")
    close(done)
}

<-terminated
fmt.Println("done")

4.4 orチャネル

1つ以上のチャネルを1つのチャネルにまとめ、どれか1つが閉じられたら全部のチャネルを閉じるようにする。 再帰をつかったorチャネルで実現できる(実装略)

4.5 エラーハンドリング

取得される結果とエラーを対にする。 http.Get(url)をするプログラムであれば、

type Result struct {
    Error error
    Response *http.Response
}

を作り、2つをまとめる。

4.6 パイプライン

  • パイプライン: データを受け取って、何らかの処理を行い、どこかに渡すという操作をまとめたもの
  • ステージ: パイプラインで行う操作1つ1つのこと
    • 受け取る型と返す型が同じ
    • 具体化されていない
    • バッチ処理とストリーム処理
      • バッチ処理: データを一気に処理
      • 要素を1つ受け取って、1つ返す