[Go] Go言語による並行処理 3章メモ2

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

3.3 チャネル

宣言、初期化

値をchan型の変数に渡し、プログラムの別の場所でその値をチャネルから読み取る。 以下で宣言、初期化ができる。

var dataStream chan interface{}
dataStream = make(chan interface{})

読み込み、書き込み

送信の場合は->をチャネルの右側に、受診の場合は->を左側に置く。

stringStream := make(chan string)
go func() {
    stringStream <- "hello" // 送信
}

fmt.Println(<-stringStream) // 受診
}

上の例で送信を行うゴルーチンが終了する前にメインゴルーチンが終了しないのは、チャネルがブロックをするからである。

  • チャネルのキャパシティがいっぱいのとき: チャネルに空きができるまで書き込みを待機
  • チャネルが空: 少なくとも1つの要素が入るまで読み込みを待機

チャネルを閉じる

s, ok := <- stringStreamとしたとき、2つ目の戻り値は読み込みができたかどうか、もしくは閉じたチャネルから生成されたデフォルト値のどちらかを示す。チャネルを閉じるのは以下で行う。

valueStream := make(chan interface{})
close(valueStream)

これにより、チャネルをループで処理することができる。

intStream := make(chan int)
go func() {
    defer close(intStream)
    for i := 1; i <= 5; i++ {
        intStream <- i
    }
}()

for integer := range intStream {
    fmt.Println("%v ", integer)
}

また、3.2節のCond型で複数のゴルーチンに同時にシグナルを送信する方法もあったが、それもチャネルで代用できる。(コード略)

バッファ付きチャネル

初期化の際にキャパシティ(nとする)が与えられたチャネルを生成することで、一度も読み込みが行われなくてもn回書き込みが可能。 バッファ付きチャネルはFIFOキューと同じ動作をする。

var dataStream chan interface{}
dataStream = make(chan interface{}, 4) // キャパシティが4

チャネルの所有権

所有権: チャネルを初期化し、書き込み、閉じるゴルーチン 単方向チャネルを宣言するとチャネルを所有するゴルーチンとチャネルを利用するだけのゴルーチンを区別できる。 プログラム内ではチャネルの所有権のスコープを小さくする。

3.4 select文

読み込みの場合、チャネルに書き込みがあったか、閉じられたかを、書き込みの場合キャパシティいっぱいになっていないかを確認する。どのチャネルも当てはまらない場合はブロックする。 case文全体に対して擬似乱数による一様選択をしており、複数のチャネルがあった場合、どのcaseが実行されるかはランダムとなる。

var c1, c2 <- chan interface{}
var c3 chan<- interface{}

select {
case <- c1:
    ...
case <- c2:
    ...
case c3 <- struct{}{}:
    ...
}

すべてのチャネルがブロックしているときに何かするにはdefault節を用いる。 通常、default節はfor-selectループの中で用いられる。