問題の経緯:
goroutine leakとは、goroutineが存在し続け、メモリ上で「意味を失った」状態(計算は終了したが、データは不要で、終了条件がない)にある状況です。メモリリークに似ていますが、実行スレッドについてのものです。これはGoにとって重要であり、負荷が高まるとリソースが枯渇する可能性があります。
問題点:
他の言語とは異なり、スレッドの直接管理は通常手動での終了を伴いますが、Goではgoroutineは簡単に開始されるものの、必ずしも正しく終了しません。一般的な誤りは、メインロジックが終了したが、goroutineが「停止」し、閉じられたチャネルからのデータを待っている場合です。
解決策:
リークを防ぐために、制御構造(context、チャネルの閉鎖、シグナル変数)を使用します。各goroutineからの出口経路を事前に設計し、クリーンアップ用のdeferを使用することが重要です。以下は例です:
func worker(ctx context.Context, jobs <-chan int, results chan<- int) { for { select { case <-ctx.Done(): return case job, ok := <-jobs: if !ok { return } results <- job * 2 } } }
重要な特徴:
Goroutineを停止するためにチャネルを単純に閉じることができますか?
必ずしもそうではありません。selectに他のcaseがある場合や、okを使用して閉鎖をチェックしない場合、goroutineは「停止」したままになる可能性があります。
val, ok := <-ch if !ok { return } // これが正確な方法
select内でcontext.Doneを処理し忘れるとどうなりますか?
goroutineはキャンセルが発生したことを知ることがなく、「永久」に残ります。これはリークの直接的な道です。
go runtimeを使用してリークを検出できますか?
リークを追跡する標準ツールはありません。runtime.NumGoroutineを通じてアクティブなgoroutineの数を監視するか、サードパーティのライブラリのリーク検出器を使用する必要があります。
プッシュ通知システムで、受信メッセージごとにgoroutineが起動されますが、コンテキストがキャンセルされた場合やチャネルが閉じられた場合にそれを停止することを忘れます。その結果、数百の「死んだ」goroutineがメモリ上に残ります。
長所:
短所:
goroutineの動作はコンテキストを通じて制御され、ビジネスロジックレベルでselectからの退出が確認され、成功した送信/処理の後にチャネルが閉じられます。
長所:
短所: