16.9 ゴルーチンリークの見つけ方

ゴルーチンリーク という言葉を聞いたことがあるかもしれません。ゴルーチンリークとは、ゴルーチンが次のどちらかの状態であることを指します。

  • 中にforループがあり、無限ループしている

  • チャネルの送受信によってブロックしている

Goはガベージコレクションのある言語でメモリリークは比較的起こりにくいのですが、ゴルーチンは回収対象になっていません。ゴルーチンは実行が完了するまで動作し続けますし、OSスレッドのように外から停止したり終了させたりすることもできないため、ゴルーチンリークはやっかいな問題を引き起こす可能性があります。

16.9.1 ループによるリーク

よくある失敗は、「16.3 ゴルーチンプールを使って複数のタスクを実行 」で紹介したゴルーチンプールの実装でありがちな、チャネルから受信してそれを処理し続けるコード、あるいは送信し続けるコードです。

たとえばチャネルから受信し続けるゴルーチンの中に無限forループがあり、その中で受信をしている場合、チャネルをクローズしたとしても確実にメモリにゴルーチンが残り続けます(これが ゴルーチンリーク です)。

このとき、そのままチャネルをクローズするとCPUを食い続ける無限ループ(ビジーループ)になります。ゴルーチンリークに対応するには、外部から何かしらの情報をゴルーチンに渡して、終了できるようにする必要があります。

	// NGなケース
	go func() {
		for {
			task := <-tasks
			// タスクを実行
		}
	}()

「ブロック解除可能にする」簡単な方法は、チャネルから受信するコードと、forを合体させることです。これであればチャネルのクローズによってゴルーチンのブロックを解除できるようになります。 ...