channelの挙動をおさらいする

2018-08-24 #go 

channel
the bed of a stream, river, or other waterway. https://www.dictionary.com/browse/channel

テレビのチャンネルのイメージが強いが本来の意味は「水路」や「経路」。霊媒を行うことを「チャネリング」と言ったりもするそうで、つまりは何かと何かをつなぐという意味合いの言葉だ。

Goにおけるchannelも、goroutine間でデータの授受を行う経路となる。

インスタンス化

最も基本的な宣言。

c := make(chan interface{})

第2引数でバッファサイズを指定できる。デフォルトは0(unbuffered channel)。 arrayはサイズまで含め型情報として扱われるがchannelはそうではない。 異なるバッファサイズのchannelを互いに代入することができる。

c := make(chan interface{}, 2)
c2 := make(chan interface{}, 3)

fmt.Println(cap(c2))  // 3
c2 = c
fmt.Println(cap(c2))  // 2

単方向channel

左側に<-をつけるとreceive-only、右側に<-をつけるとsend-onlyのchannelになる。

c := make(chan interface{})
var receiveChan <-chan interface{}
var sendChan chan<- interface{}

chanから単方向channelへは暗黙的に変換することができる(逆はだめ)。

go func() {
  sendChan <- 3
}()

receiveChan = c
sendChan = c

fmt.Println(<-receiveChan)  // 3

単方向channelは型のようなもので、functionの戻り値としても使える。これはデータフローの主従関係を明示するのに役立つ。

func main() {
  for i := range generator() {
    fmt.Println(i)
  }
}

func generator() <-chan interface{} {
  c := make(chan interface{})

  go func() {
    defer close(c)
    for i := 0; i < 5; i++ {
      c <- i
    }
  }()

  return c
}

closeする

channelからは2値を受け取ることができる。2つ目はchannelが開いているかを表している。

go func() {
  c <- 1
}()

v, ok := <-c  // 1 true

close(chan)でchannelを閉じることができる。

close(c)
v, ok := <-c  // <nil> false

closeしたchannelにデータを送ることはできない。

c := make(chan interface{}, 1)
close(c)
c <- 1  // panic: send on closed channel

そして未送信のデータがあるうちはcloseされない。

c := make(chan interface{})

go func() {
  defer close(c)
  c <- 1
}()

v, ok := <-c  // 1 true
v, ok = <-c   // <nil> false

channelをrangeでループするときなんかにcloseは重要になる。

go func() {
  defer close(c)
  for i := 0; i < 3; i++ {
    c <- i
  }
}()

for i := range c {
  // 0
  // 1
  // 2
  fmt.Println(i)
}

もしcloseしないとループでブロックされ続けてしまうからだ。

0
1
2
fatal error: all goroutines are asleep - deadlock!

nilのchannelやcloseされたchannelをcloseするとpanicになる。

var c chan interface{}
// panic: close of nil channel
close(c)

またreceive-only channelをcloseするのはコンパイルエラーとなる。 channelをcloseするのはsend側の役目だ。

receiveChan := make(<-chan interface{})

// invalid operation: close(receiveChan) (cannot close receive-only channel)
close(receiveChan)