😵

Go で1行でgoroutineデッドロックを起こす方法

2022/04/17に公開1

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 起こせるやん!」と気づいて記事を書き始めたらついつい色々足しちゃいましたが、こんな感じで思いつきで書いていこうかなと思うのでよろしくお願いします!)

脚注
  1. この辺とか https://go.dev/tour/concurrency/1 ↩︎

  2. 他の参考資料 https://stackoverflow.com/a/53388311 ↩︎

Discussion