Go で1行でgoroutineデッドロックを起こす方法
1行で goroutine デッドロックを起こす
本当に1行
package main
func main() {
<-make(chan struct{})
}
ちょっと見やすくしたやつ
package main
func main() {
forever := make(chan struct{})
<-forever
}
結果
コンパイル時にはエラーは起きず、runtime で(つまり、実行時に)デッドロックが起きます。
$ go run ./main.go
fatal error: all goroutines are asleep - deadlock!
意味
見たままですが、これだけだと アレ なので解説的なものも書いてみます。
ちょっと見やすくしたやつ バージョンを再掲します。
package main
func main() {
forever := make(chan struct{})
<-forever
}
1行ごとみてみましょう。
forever := make(chan struct{})
struct{}
をやり取りできるチャンネルを forever
という名前で初期化します。
<-forever
forever
チャンネルに struct{}
が渡されるのを待ちます。
このプログラムで、実行されている goroutine は main()
だけですので、
forever
チャンネルに struct{}
の値を渡す者は存在しません。
- goroutine (1個)
-
main()
← 待機中!
-
したがって、 all goroutines are asleep と判定され、 fatal error となってプログラムが異常終了します。
deadlock をしっかり検知してくれて偉いですね。
使い所
さすがに <-make(chan struct{})
の使い所はなさそうです。
forever
バージョンの方は debug 用途とかで使えるかもしれません。
やはり、 生きていると、 goroutine で無限ループを回したくなる と思いますが、以下のコードだと、 main()
が終わると、起動中の goroutine もろとも終了されてしまいます。
package main
func main() {
go func() {
for {
// 謎の無限ループ
}
}()
// 無限ループごと終了しちゃう(泣)
}
そこで、 forever
チャンネルを開いておくことで、 main()
が終了することなく、 とことん無限ループできます 。
package main
func main() {
forever := make(chan struct{})
go func() {
for {
// 謎の無限ループ
}
}()
// 終わらないよぉ〜
<-forever
}
さっきは、 main()
だけだったので、 main()
が待機状態になると即座に deadlock 判定されてしまいました。
今回の場合、 main()
と別に goroutine が実行中なので、 deadlock とは判定されなくなります。
- goroutine (2個)
-
main()
← 待機中! -
無限ループ goroutine
← 実行中!
-
goroutine の deadlock は、 runtime で(つまり、実行時に)判定されるものだからこそ、このような挙動の違いになるわけですね。
さいごに
goroutine やチャンネルの挙動は慣れるまで「?」となることが多いですが、こんな感じでローカルで遊んでいると感覚を掴めてきます。
是非お試しあれ。
(「1行で deadlock 起こせるやん!」と気づいて記事を書き始めたらついつい色々足しちゃいましたが、こんな感じで思いつきで書いていこうかなと思うのでよろしくお願いします!)
Discussion
意外と起こせるデッドロック、こういうのもできますね 😃