all goroutines are asleep - deadlock! の原因と直し方
バッファなしチャネルへの送信は、受信するgoroutineが存在するまでブロックします。受信側のgoroutineを起動し忘れると、送信だけが永遠にブロックしデッドロックとして検出されます。
エラーメッセージの読み方
fatal error: all goroutines are asleep - deadlock!
fatal error- ランタイムが検出した致命的な状態 — panicとは別に、goroutineの実行が続けられなくなったことをGoランタイム自身が検出したことを示します
all goroutines are asleep - deadlock!- 詳細メッセージ — 何が起きたか
このエラーが出る典型パターン
パターン1
1 package main 2 3 func main() { 4 ch := make(chan int) 5 ^ 6 <-ch 7 }
バッファなしチャネルへの受信は、送信するgoroutineが無いと永遠にブロックします。goで送信側を別goroutineとして起動する必要があります。
直し方: (空) を go func() { ch <- 1 }() にします。
パターン2
1 package main 2 3 func main() { 4 ch := make(chan string) 5 ^ 6 <-ch 7 }
送信をgoroutineにせず同じgoroutineでch <- "go"と書いても、受信側が無いので同様にブロックしたままです。
直し方: (空) を go func() { ch <- "go" }() にします。
パターン3
1 package main 2 3 func main() { 4 ch := make(chan bool) 5 ^ 6 <-ch 7 }
デッドロックはpanicではなくfatal errorとしてGoランタイム自身が検出し、プロセス全体を終了させます。
直し方: (空) を go func() { ch <- true }() にします。
パターン4
1 package main 2 3 func main() { 4 ch := make(chan int) 5 ^ 6 <-ch 7 }
送信も受信もされないままメインgoroutineが止まると、他に動けるgoroutineが無いためランタイムがデッドロックと判断します。
直し方: (空) を go func() { ch <- 42 }() にします。
パターン5
1 package main 2 3 func main() { 4 ch := make(chan float64) 5 ^ 6 <-ch 7 }
型がfloat64でも同じで、受信を待つ側だけがいて送信するgoroutineが存在しなければ必ず止まります。
直し方: (空) を go func() { ch <- 3.14 }() にします。
よくある誤解
「チャネルへの送信は、受け取り手がいなくても一旦キューに積まれて先に進めるはず」という思い込みは誤りです。バッファなしチャネルは送信と受信が同時に成立して初めて完了する同期的な操作で、受信側が居ないと送信側は永遠にブロックします。
まとめ
all goroutines are asleep - deadlock!は中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。