🔃
Goのデッドロックを理解する:バッファ無しチャネルの落とし穴
次のようなコードを書くとデッドロックが発生します。
package main
import "fmt"
func main() {
ch := make(chan int)
// チャネルに送信
ch <- 1
// チャネルから受信
fmt.Println(<-ch)
}
実行結果:
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.main()
/××××××/main.go:7 +0x38
exit status 2
なぜデッドロックが起きるのか?
ここで作られている ch は バッファ無しチャネル です。
バッファ無しチャネルでは、
- 送信
ch <- v - 受信
v := <-ch
の 両方が同時に揃わない限り処理が進みません。
つまり、送信側は「受信者が準備できるまで永久に待つ」動作になります。
このコードでは main ゴルーチンしか存在しないため、
-
ch <- 1で送信しようとする - 受信してくれる相手がまだいないため main が停止
- main が止まったので、その後の
fmt.Println(<-ch)に到達できない - 全ゴルーチン(= main)が待ち状態
という流れになります。
デッドロックを回避するには
送信と受信を別ゴルーチンで実行し、両方が同時に成立できるようにします。
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 1 // 別ゴルーチンで送信
}()
fmt.Println(<-ch) // main で受信
}
これなら、
- main が受信で待つ
- 別ゴルーチンが送信してくれる
という状態が作れるので、デッドロックにはなりません。
注意
逆に次のコードはデッドロックになります。
package main
import "fmt"
func main() {
ch := make(chan int)
// チャネルに送信
ch <- 1
go func() {
// チャネルから受信
fmt.Println(<-ch)
}()
}
理由:
-
ch <- 1の時点で main がブロックしてしまい、 -
go func(){}の行に永遠に到達しないため、受信ゴルーチンが起動せずデッドロックが発生します。
つまり 順番の問題 で送受信が成立しません。
(補足)バッファ付きチャネルなら成立する
ch := make(chan int, 1)
とすると、
ch <- 1 はバッファに入ってブロックされないので、下のゴルーチンが後から読み出してもデッドロックしません。
Discussion