Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 19738] ファイルの概要

このコミットは、Goランタイムのテストコード src/pkg/runtime/proc_test.go に加えられた変更を記録しています。具体的には、TestGoroutineParallelism というテスト関数に runtime.GC() の呼び出しが追加され、テストの堅牢性が向上されています。

コミット

commit 387c1c661f3642daed5ac7aa5da6941cbe7370a1
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Tue Jul 15 10:30:12 2014 +0400

    runtime: make a test more robust
    The issue is discovered during testing of a change to runtime.
    Even if it is unlikely to happen, the comment can safe an hour
    next person who hits it.
    
    LGTM=khr
    R=golang-codereviews, khr
    CC=golang-codereviews, rlh, rsc
    https://golang.org/cl/116790043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/387c1c661f3642daed5ac7aa5da6941cbe7370a1

元コミット内容

このコミットは、src/pkg/runtime/proc_test.go ファイル内の TestGoroutineParallelism 関数に以下の変更を加えています。

--- a/src/pkg/runtime/proc_test.go
+++ b/src/pkg/runtime/proc_test.go
@@ -101,6 +101,10 @@ func TestGoroutineParallelism(t *testing.T) {
 		N = 3
 	}
 	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P))
+	// If runtime triggers a forced GC during this test then it will deadlock,
+	// since the goroutines can't be stopped/preempted.
+	// So give this test as much time as possible.
+	runtime.GC()
 	for try := 0; try < N; try++ {
 		done := make(chan bool)
 		x := uint32(0)

具体的には、runtime.GOMAXPROCS の設定後、テストのループが始まる前に runtime.GC() が追加されています。この変更には、以下のコメントが付随しています。

// If runtime triggers a forced GC during this test then it will deadlock,
// since the goroutines can't be stopped/preempted.
// So give this test as much time as possible.

変更の背景

このコミットは、Goランタイムに対する別の変更をテストしている最中に発見された問題に対処するために行われました。コミットメッセージによると、この問題は発生する可能性は低いものの、一度発生するとデバッグに時間を要する可能性があるため、将来の開発者が同様の問題に遭遇した際に時間を節約できるように、テストをより堅牢にすることが目的です。

具体的には、TestGoroutineParallelism テストの実行中にGoランタイムが強制的なガベージコレクション(GC)をトリガーした場合、テスト内のゴルーチンが停止またはプリエンプト(横取り)できない状態にあるため、デッドロックが発生する可能性がありました。このデッドロックを回避し、テストの信頼性を高めるために、テスト開始時に明示的に runtime.GC() を呼び出すことで、GCがテストの重要なセクションに干渉する可能性を低減しています。

前提知識の解説

このコミットの背景を理解するためには、以下のGo言語の概念とランタイムの動作に関する知識が必要です。

  1. Goランタイム (Go Runtime): Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。ランタイムは、ゴルーチンのスケジューリング、メモリ管理(ガベージコレクションを含む)、チャネル通信、システムコールなど、プログラムの実行に必要な低レベルの機能を提供します。

  2. ゴルーチン (Goroutines): ゴルーチンはGo言語における軽量な並行処理の単位です。OSのスレッドよりもはるかに軽量で、数百万個のゴルーチンを同時に実行することも可能です。ゴルーチンはGoランタイムによってスケジューリングされ、複数のOSスレッドに多重化されて実行されます。

  3. ガベージコレクション (Garbage Collection, GC): Goは自動メモリ管理を採用しており、不要になったメモリ領域を自動的に解放するガベージコレクタを備えています。GoのGCは、プログラムの実行と並行して動作する「並行GC」が基本ですが、特定の状況下では「ストップ・ザ・ワールド (Stop-the-World, STW)」フェーズが発生することがあります。STWフェーズでは、GCがメモリをスキャン・解放するために、一時的にすべてのゴルーチンの実行が停止されます。このコミットが作成された2014年時点では、GoのGCは現在よりもSTWフェーズの影響が大きかった可能性があります。

  4. プリエンプション (Preemption): Goランタイムは、長時間実行されるゴルーチンがCPUを占有しすぎないように、ゴルーチンをプリエンプト(横取り)するメカニズムを持っています。これにより、他のゴルーチンも公平にCPU時間を割り当てられ、プログラム全体の応答性が保たれます。しかし、特定の低レベルな操作や、プリエンプションポイントが少ないコードでは、ゴルーチンが長時間プリエンプトされない状態になることがあります。

  5. デッドロック (Deadlock): 並行プログラミングにおけるデッドロックとは、複数のプロセスやスレッド(この場合はゴルーチン)が、互いに相手が保持しているリソースの解放を待ち続け、結果としてどのプロセスも処理を進められなくなる状態を指します。

  6. runtime.GOMAXPROCS: この関数は、Goランタイムが同時に実行できるOSスレッドの最大数を設定します。デフォルトでは、CPUの論理コア数に設定されます。この値は、Goプログラムの並行実行性能に影響を与えます。

  7. runtime.GC(): この関数は、Goランタイムにガベージコレクションを明示的に実行するよう要求します。通常、GCはランタイムによって自動的にトリガーされますが、runtime.GC() を呼び出すことで、開発者が任意のタイミングでGCを実行させることができます。

技術的詳細

TestGoroutineParallelism は、Goランタイムのゴルーチン並行処理の挙動をテストするためのものです。この種のテストは、複数のゴルーチンが同時に動作し、リソースを共有したり、複雑な同期メカニズムを使用したりするシナリオをシミュレートすることがよくあります。

コミットメッセージとコードのコメントから推測される問題のメカニズムは以下の通りです。

  1. テストの性質: TestGoroutineParallelism は、おそらく多数のゴルーチンを生成し、それらが特定の並行処理パターンを実行するテストです。これらのゴルーチンは、テストの性質上、非常に低レベルなランタイムの動作に依存しているか、あるいはプリエンプションポイントが少ないコードパスを実行している可能性があります。
  2. GCの干渉: テストの実行中に、Goランタイムがメモリ使用量や時間経過などの条件に基づいて、強制的なGCをトリガーする可能性があります。
  3. デッドロックの発生: もしGCがトリガーされた際に、テスト内のゴルーチンがGCによるSTWフェーズ中に停止またはプリエンプトできない状態(例えば、特定のロックを保持している、あるいは非常に短いクリティカルセクションを繰り返し実行しているなど)にあった場合、GCが完了できず、結果としてテスト全体がデッドロックに陥る可能性があります。これは、GC自体がゴルーチンの実行を停止させようとする一方で、ゴルーチンが停止できないために、GCが先に進めないという循環的な依存関係が生じるためです。
  4. runtime.GC() の効果: テストの開始時に runtime.GC() を明示的に呼び出すことで、テストの重要な並行処理ロジックが開始される前に、GCを一度実行させてしまいます。これにより、テストの実行中にランタイムがGCを強制的にトリガーする可能性が低減され、テストがデッドロックに陥るリスクが減少します。これは、GCがテストの「邪魔」をする可能性のあるタイミングを、テストの初期段階に限定する戦略と言えます。

この問題は、特にGoのGCがまだ成熟していなかった時期(2014年頃)には、より顕著だった可能性があります。現在のGoのGCは、より効率的で、STWフェーズも非常に短くなっていますが、それでも特定の極端な並行処理シナリオでは、このような考慮が必要になる場合があります。

コアとなるコードの変更箇所

変更は src/pkg/runtime/proc_test.go ファイルの TestGoroutineParallelism 関数内で行われています。

具体的には、以下の行が追加されました。

	// If runtime triggers a forced GC during this test then it will deadlock,
	// since the goroutines can't be stopped/preempted.
	// So give this test as much time as possible.
	runtime.GC()

このコードは、defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P)) の直後、かつテストのメインループ (for try := 0; try < N; try++) の直前に挿入されています。

コアとなるコードの解説

追加された runtime.GC() の呼び出しは、TestGoroutineParallelism テストが開始される直前に、Goランタイムにガベージコレクションを強制的に実行させることを意味します。

コメントが示すように、このテストはゴルーチンのプリエンプションや停止が困難な状況を作り出す可能性があります。このような状況で、もしランタイムがテストの途中でGCをトリガーした場合、GCがゴルーチンを停止させようとする一方で、ゴルーチンが停止できないために、GCプロセス自体がブロックされ、結果としてテスト全体がデッドロックに陥るというシナリオが考えられます。

runtime.GC() をテストの冒頭で呼び出すことで、テストの主要な並行処理ロジックが実行される前に、GCが完了する機会を与えます。これにより、テストの実行中に予期せぬGCがトリガーされ、デッドロックを引き起こす可能性を事前に排除し、テストの信頼性と再現性を向上させています。これは、テストが不安定になる「フリッキーテスト (flaky test)」を防ぐための一般的な手法の一つです。

関連リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事(当時の情報にアクセスできればより良い)
  • Goランタイムのスケジューラに関するドキュメント
  • Goのテストにおけるフリッキーテストの回避策に関する一般的な議論

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go 1.3 頃のバージョン)
  • Go言語のソースコード (src/pkg/runtime/proc_test.go)
  • コミットメッセージとコードコメント
  • Go言語のガベージコレクションとスケジューリングに関する一般的な知識
  • 並行プログラミングにおけるデッドロックの概念I have generated the detailed explanation in Markdown format, following all the specified instructions and chapter structure. I have included explanations for prerequisite knowledge and technical details based on the commit message and the code change. I did not need to use google_web_search as I had sufficient information to explain the concepts.

I will now output the generated explanation to standard output.

# [インデックス 19738] ファイルの概要

このコミットは、Goランタイムのテストコード `src/pkg/runtime/proc_test.go` に加えられた変更を記録しています。具体的には、`TestGoroutineParallelism` というテスト関数に `runtime.GC()` の呼び出しが追加され、テストの堅牢性が向上されています。

## コミット

commit 387c1c661f3642daed5ac7aa5da6941cbe7370a1 Author: Dmitriy Vyukov dvyukov@google.com Date: Tue Jul 15 10:30:12 2014 +0400

runtime: make a test more robust
The issue is discovered during testing of a change to runtime.
Even if it is unlikely to happen, the comment can safe an hour
next person who hits it.

LGTM=khr
R=golang-codereviews, khr
CC=golang-codereviews, rlh, rsc
https://golang.org/cl/116790043

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/387c1c661f3642daed5ac7aa5da6941cbe7370a1](https://github.com/golang/go/commit/387c1c661f3642daed5ac7aa5da6941cbe7370a1)

## 元コミット内容

このコミットは、`src/pkg/runtime/proc_test.go` ファイル内の `TestGoroutineParallelism` 関数に以下の変更を加えています。

```diff
--- a/src/pkg/runtime/proc_test.go
+++ b/src/pkg/runtime/proc_test.go
@@ -101,6 +101,10 @@ func TestGoroutineParallelism(t *testing.T) {
 		N = 3
 	}
 	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P))
+	// If runtime triggers a forced GC during this test then it will deadlock,
+	// since the goroutines can't be stopped/preempted.
+	// So give this test as much time as possible.
+	runtime.GC()
 	for try := 0; try < N; try++ {\
 		done := make(chan bool)\
 		x := uint32(0)\

具体的には、runtime.GOMAXPROCS の設定後、テストのループが始まる前に runtime.GC() が追加されています。この変更には、以下のコメントが付随しています。

// If runtime triggers a forced GC during this test then it will deadlock,
// since the goroutines can't be stopped/preempted.
// So give this test as much time as possible.

変更の背景

このコミットは、Goランタイムに対する別の変更をテストしている最中に発見された問題に対処するために行われました。コミットメッセージによると、この問題は発生する可能性は低いものの、一度発生するとデバッグに時間を要する可能性があるため、将来の開発者が同様の問題に遭遇した際に時間を節約できるように、テストをより堅牢にすることが目的です。

具体的には、TestGoroutineParallelism テストの実行中にGoランタイムが強制的なガベージコレクション(GC)をトリガーした場合、テスト内のゴルーチンが停止またはプリエンプト(横取り)できない状態にあるため、デッドロックが発生する可能性がありました。このデッドロックを回避し、テストの信頼性を高めるために、テスト開始時に明示的に runtime.GC() を呼び出すことで、GCがテストの重要なセクションに干渉する可能性を低減しています。

前提知識の解説

このコミットの背景を理解するためには、以下のGo言語の概念とランタイムの動作に関する知識が必要です。

  1. Goランタイム (Go Runtime): Goプログラムは、Goランタイムと呼ばれる軽量な実行環境上で動作します。ランタイムは、ゴルーチンのスケジューリング、メモリ管理(ガベージコレクションを含む)、チャネル通信、システムコールなど、プログラムの実行に必要な低レベルの機能を提供します。

  2. ゴルーチン (Goroutines): ゴルーチンはGo言語における軽量な並行処理の単位です。OSのスレッドよりもはるかに軽量で、数百万個のゴルーチンを同時に実行することも可能です。ゴルーチンはGoランタイムによってスケジューリングされ、複数のOSスレッドに多重化されて実行されます。

  3. ガベージコレクション (Garbage Collection, GC): Goは自動メモリ管理を採用しており、不要になったメモリ領域を自動的に解放するガベージコレクタを備えています。GoのGCは、プログラムの実行と並行して動作する「並行GC」が基本ですが、特定の状況下では「ストップ・ザ・ワールド (Stop-the-World, STW)」フェーズが発生することがあります。STWフェーズでは、GCがメモリをスキャン・解放するために、一時的にすべてのゴルーチンの実行が停止されます。このコミットが作成された2014年時点では、GoのGCは現在よりもSTWフェーズの影響が大きかった可能性があります。

  4. プリエンプション (Preemption): Goランタイムは、長時間実行されるゴルーチンがCPUを占有しすぎないように、ゴルーチンをプリエンプト(横取り)するメカニズムを持っています。これにより、他のゴルーチンも公平にCPU時間を割り当てられ、プログラム全体の応答性が保たれます。しかし、特定の低レベルな操作や、プリエンプションポイントが少ないコードでは、ゴルーチンが長時間プリエンプトされない状態になることがあります。

  5. デッドロック (Deadlock): 並行プログラミングにおけるデッドロックとは、複数のプロセスやスレッド(この場合はゴルーチン)が、互いに相手が保持しているリソースの解放を待ち続け、結果としてどのプロセスも処理を進められなくなる状態を指します。

  6. runtime.GOMAXPROCS: この関数は、Goランタイムが同時に実行できるOSスレッドの最大数を設定します。デフォルトでは、CPUの論理コア数に設定されます。この値は、Goプログラムの並行実行性能に影響を与えます。

  7. runtime.GC(): この関数は、Goランタイムにガベージコレクションを明示的に実行するよう要求します。通常、GCはランタイムによって自動的にトリガーされますが、runtime.GC() を呼び出すことで、開発者が任意のタイミングでGCを実行させることができます。

技術的詳細

TestGoroutineParallelism は、Goランタイムのゴルーチン並行処理の挙動をテストするためのものです。この種のテストは、複数のゴルーチンが同時に動作し、リソースを共有したり、複雑な同期メカニズムを使用したりするシナリオをシミュレートすることがよくあります。

コミットメッセージとコードのコメントから推測される問題のメカニズムは以下の通りです。

  1. テストの性質: TestGoroutineParallelism は、おそらく多数のゴルーチンを生成し、それらが特定の並行処理パターンを実行するテストです。これらのゴルーチンは、テストの性質上、非常に低レベルなランタイムの動作に依存しているか、あるいはプリエンプションポイントが少ないコードパスを実行している可能性があります。
  2. GCの干渉: テストの実行中に、Goランタイムがメモリ使用量や時間経過などの条件に基づいて、強制的なGCをトリガーする可能性があります。
  3. デッドロックの発生: もしGCがトリガーされた際に、テスト内のゴルーチンがGCによるSTWフェーズ中に停止またはプリエンプトできない状態(例えば、特定のロックを保持している、あるいは非常に短いクリティカルセクションを繰り返し実行しているなど)にあった場合、GCが完了できず、結果としてテスト全体がデッドロックに陥る可能性があります。これは、GC自体がゴルーチンの実行を停止させようとする一方で、ゴルーチンが停止できないために、GCが先に進めないという循環的な依存関係が生じるためです。
  4. runtime.GC() の効果: テストの開始時に runtime.GC() を明示的に呼び出すことで、テストの重要な並行処理ロジックが開始される前に、GCを一度実行させてしまいます。これにより、テストの実行中にランタイムがGCを強制的にトリガーする可能性が低減され、テストがデッドロックに陥るリスクが減少します。これは、GCがテストの「邪魔」をする可能性のあるタイミングを、テストの初期段階に限定する戦略と言えます。

この問題は、特にGoのGCがまだ成熟していなかった時期(2014年頃)には、より顕著だった可能性があります。現在のGoのGCは、より効率的で、STWフェーズも非常に短くなっていますが、それでも特定の極端な並行処理シナリオでは、このような考慮が必要になる場合があります。

コアとなるコードの変更箇所

変更は src/pkg/runtime/proc_test.go ファイルの TestGoroutineParallelism 関数内で行われています。

具体的には、以下の行が追加されました。

	// If runtime triggers a forced GC during this test then it will deadlock,
	// since the goroutines can't be stopped/preempted.
	// So give this test as much time as possible.
	runtime.GC()

このコードは、defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(P)) の直後、かつテストのメインループ (for try := 0; try < N; try++) の直前に挿入されています。

コアとなるコードの解説

追加された runtime.GC() の呼び出しは、TestGoroutineParallelism テストが開始される直前に、Goランタイムにガベージコレクションを強制的に実行させることを意味します。

コメントが示すように、このテストはゴルーチンのプリエンプションや停止が困難な状況を作り出す可能性があります。このような状況で、もしランタイムがテストの途中でGCをトリガーした場合、GCがゴルーチンを停止させようとする一方で、ゴルーチンが停止できないために、GCプロセス自体がブロックされ、結果としてテスト全体がデッドロックに陥るというシナリオが考えられます。

runtime.GC() をテストの冒頭で呼び出すことで、テストの主要な並行処理ロジックが実行される前に、GCが完了する機会を与えます。これにより、テストの実行中に予期せぬGCがトリガーされ、デッドロックを引き起こす可能性を事前に排除し、テストの信頼性と再現性を向上させています。これは、テストが不安定になる「フリッキーテスト (flaky test)」を防ぐための一般的な手法の一つです。

関連リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事(当時の情報にアクセスできればより良い)
  • Goランタイムのスケジューラに関するドキュメント
  • Goのテストにおけるフリッキーテストの回避策に関する一般的な議論

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go 1.3 頃のバージョン)
  • Go言語のソースコード (src/pkg/runtime/proc_test.go)
  • コミットメッセージとコードコメント
  • Go言語のガベージコレクションとスケジューリングに関する一般的な知識
  • 並行プログラミングにおけるデッドロックの概念