概要
Go 言語の仕様まとめ。
内容
- goroutine と channel
- channelの基本動作
- goroutine + channel
- close
- select
goroutine と channel
- channelの基本操作
// 変数名 + chan + データ型
var ch chan int
// make関数でchannelとしての機能をもたせる(送受信どちらも可能)
ch = make(chan int)
// 宣言と同時にchannelとしての機能をもたせる(送受信どちらも可能)
ch2 := make(chan int)
// 送信専用channel
var sendChan <-chan int
// 受信専用channel
var receiveChan chan<- int
// bufferサイズ(容量)を調べるには、cap関数を使う
var ch chan int
fmt.Println(cap(ch)) // 0
// make関数の第2引数でbufferサイズを設定できる
ch2 := make(chan int, 5)
fmt.Println(cap(ch2)) // 5
// 送信
ch := make(chan int, 5)
ch <- 100
// データを一つ送信したため、chのデータ数は1
fmt.Println(len(ch)) // 1
// 送信したデータを受信する
fmt.Println(<-ch) // 100
// データを受信したため、データ数は0
fmt.Println(len(ch)) // 0
// データ数0の状態でさらにデータを受信しようとするとエラー
fmt.Println(<-ch) // fatal error: all goroutines are asleep - deadlock!
// 送信したデータの受信先を変数にもできる
ch2 := make(chan int, 5)
ch2 <- 100
ch2 <- 200
ch2 <- 300
i := <-ch2
// データの受信は、送信した順になる
fmt.Println(i) // 100
i = <-ch2
fmt.Println(i) // 200
// また、bufferサイズを超えた数のデータを送信しようとするとエラーとなる
ch3 := make(chan int, 3)
ch3 <- 10
ch3 <- 20
ch3 <- 30
ch3 <- 40 // fatal error: all goroutines are asleep - deadlock!
- goroutine + channel
channelは、goroutine間でデータの送受信をするために用いられる。
func goroutine(c chan string) {
for {
// 3. 送信されたデータを受信する
fmt.Println(<-c)
}
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
// 2. goroutineが走る
go goroutine(ch1)
go goroutine(ch2)
for i := 0; i < 10; i++ {
// 1. channelにデータ送信
ch1 <- "Hello"
ch2 <- "Golang"
time.Sleep(50 * time.Millisecond)
}
}
Hello
Golang
Hello
Golang
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
Golang
Hello
- close
生成したchannelは、初期状態ではオープン状態だが、明示的にクローズ状態にできる。
// makeで作成したchannelはオープン状態
ch := make(chan int, 1)
// close関数の引数にchannelを渡して、明示的にクローズする
close(ch)
// クローズしたchannelに対してデータを送信するとruntime errorになる
ch <- 1 // panic: send on closed channel
クローズしたchannelに対して送信は不可だが、送信は可。
ch := make(chan int, 1)
close(ch)
// クローズしたchannelからの受信は可能
fmt.Println(<-ch) // 0
受信の場合は、2つの変数を割り当てられる。
2つ目の変数には、boolでchannelのオープン/クローズ状態を受け取れる。
ch := make(chan int, 1)
ch <- 1
i, ok := <-ch
// オープン状態のため、true
fmt.Println(i, ok) // 1 true
// channelをクローズ
close(ch)
i, ok := <-ch
// クローズしたため、false
fmt.Println(i, ok) // 0 false
// ※ 正確には、channelのbufferが空且つクローズ状態がfalseが返ってくる条件
ch2 := make(chan int, 2)
ch2 <- 2
close(ch3)
i2, ok := <-ch2
// channelはクローズしたが、データを受信していて空ではないため、trueとなる
fmt.Println(i2, ok) // 3 true
channelをrangeでループする際の注意点。
明示的にクローズしないと、受信している値をすべて取り出した後もループは続くため、エラーになる。
クローズしないと、4回目のループでdeadlockがかかるため、
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
for i := range ch {
fmt.Println(i)
// 1
// 2
// 3
// fatal error: all goroutines are asleep - deadlock!
}
}
closeしておく必要がある。
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
for i := range ch {
fmt.Println(i)
// 1
// 2
// 3
}
}
goroutineとの組み合わせで動作確認。
100回データの受信をループさせて、goroutineで受信したデータをchannelがクローズするまで出力してみる。
func test(c chan int, s string) {
for {
i, ok := <-c
// channelがクローズしたら、break
if !ok {
break
}
fmt.Println(i, s)
}
// 最後にENDを出力
fmt.Println("END")
}
func main() {
ch1 := make(chan int, 2)
go test(ch1, "Goloang")
go test(ch1, "Java")
go test(ch1, "Python")
for i := 0; i < 100; i++ {
// channelにデータが送信されたら、3つのgoroutineのどれかが動く
ch1 <- i
}
// ループが終わったらクローズする
close(ch1)
// closeした後にgoroutineの処理完了までちょっと待つ
time.Sleep(1 * time.Second)
}
- select
複数のchannelの送受信を行う際に使う構文。
selectなしで、複数のchannelを扱おうとする場合、
以下のように、どれか一つでも送受信に失敗すると後続の処理が実行されなくなってしまう。
func main() {
ch1 := make(chan int, 3)
ch2 := make(chan string, 3)
// ch2のみデータを送信する
ch2 <- "Golang"
// ch1は空なので、エラー
fmt.Println(<-ch1) // fatal error: all goroutines are asleep - deadlock!
// データは受信しているが、ch1でエラーになり、処理が止まってしまう
fmt.Println(<-ch2)
}
上記を回避するために、selectを使うことで、複数のchannelをgoroutineを停止させることなく、実行できる。
func main() {
ch1 := make(chan int, 3)
ch2 := make(chan string, 3)
// ch2のみデータを送信する
ch2 <- "Golang"
// switchとは違い、どのcaseが実行されるかはランダム
select {
// ch1に受信時の処理
case v1 := <-ch1:
fmt.Println(v1)
// ch2に受信時の処理
case v2 := <-ch2:
fmt.Println(v2)
// caseに当てはまらない時の処理
default:
fmt.Println("default")
}
}