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

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

このコミットは、Go言語のランタイムにおける以前の変更(CL 7310096 / 59da6744d66d)を取り消すものです。この取り消しは、元の変更がWindowsビルドを壊したために行われました。具体的には、src/pkg/runtime/proc.c に8行のコードが追加され、src/pkg/runtime/proc_test.go から30行のテストコードが削除されています。

コミット

commit 60526ca6d105ee5cf79ede6b964de30ceea79fd3
Author: Russ Cox <rsc@golang.org>
Date:   Fri Feb 15 17:54:46 2013 -0500

    undo CL 7310096 / 59da6744d66d
    
    broke windows build
    
    ««« original CL description
    runtime: ensure forward progress of runtime.Gosched() for locked goroutines
    The removed code leads to the situation when M executes the same locked G again and again.
    Fixes #4820.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7310096
    »»»
    
    TBR=dvyukov
    CC=golang-dev
    https://golang.org/cl/7343050

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

https://github.com/golang/go/commit/60526ca6d105ee5cf79ede6b964de30ceea79fd3

元コミット内容

このコミットは、以下の元のコミット(CL 7310096 / 59da6744d66d)を取り消すものです。

runtime: ensure forward progress of runtime.Gosched() for locked goroutines
The removed code leads to the situation when M executes the same locked G again and again.
Fixes #4820.

元のコミットは、runtime.Gosched() がロックされたゴルーチンに対して常に進行を保証するようにすることを目的としていました。これは、特定の状況下でM(マシン、OSスレッド)が同じロックされたG(ゴルーチン)を繰り返し実行し、進行が妨げられる問題を解決しようとしたものです。この問題はGoのIssue #4820として報告されていました。

変更の背景

このコミットの背景には、元の変更(CL 7310096)がWindowsビルドを壊してしまったという問題があります。Go言語はクロスプラットフォーム対応を重視しており、特定のプラットフォームでビルドが失敗することは許容されません。そのため、元の変更が意図した問題を解決しつつも、Windows環境での互換性を損ねたため、その変更を取り消す必要が生じました。

元の変更は、runtime.Gosched() の動作、特にロックされたゴルーチン(runtime.LockOSThread() によってOSスレッドに固定されたゴルーチン)のスケジューリングに関するものでした。このようなゴルーチンが無限ループに陥るような状況を回避し、確実に他のゴルーチンにCPU時間を譲る(yieldする)ことを保証しようとしました。しかし、この修正がWindowsの特定のOSスレッド管理やスケジューリングの挙動と衝突し、ビルドエラーやランタイムエラーを引き起こしたと考えられます。

前提知識の解説

このコミットを理解するためには、以下のGo言語のランタイムとスケジューリングに関する基本的な概念を理解しておく必要があります。

  • Goroutine (G): Go言語における軽量な実行単位です。OSスレッドよりもはるかに軽量で、数百万個を同時に実行することも可能です。Goランタイムが管理します。
  • Logical Processor (P): ゴルーチンを実行するためのコンテキストです。Goランタイムは、OSスレッド(M)上でPをスケジュールし、Pがゴルーチンを実行します。Pは、実行可能なゴルーチンをキューから取得し、実行します。
  • Machine (M): OSスレッドに相当します。Goランタイムは、MをOSに要求し、M上でPとGを実行します。
  • Scheduler: Goランタイムの重要なコンポーネントで、ゴルーチンをPに割り当て、M上で実行されるようにスケジュールします。
  • runtime.Gosched(): 現在のゴルーチンがCPUを自発的に手放し、他の実行可能なゴルーチンにCPU時間を譲るようにスケジューラにヒントを与える関数です。これにより、協調的なマルチタスクが実現されます。
  • runtime.LockOSThread(): 現在のゴルーチンを現在のOSスレッド(M)に固定する関数です。このゴルーチンは、他のOSスレッドに移動することなく、そのOSスレッド上で実行され続けます。これは、特定のOS API(例: GUIライブラリやCgo呼び出し)がスレッド固定を要求する場合に必要となります。
  • Issue #4820: このコミットで言及されているGoのバグトラッカーのIssueです。元のコミットが解決しようとした問題の詳細が記述されています。通常、ロックされたゴルーチンが runtime.Gosched() を呼び出しても、同じOSスレッドに固定されているため、他のゴルーチンにCPUを譲ることができず、デッドロックや進行の停滞を引き起こす可能性がありました。

技術的詳細

このコミットは、元の変更が導入した src/pkg/runtime/proc.c 内のコードを元に戻し、関連するテストを削除することで、Windowsビルドの破損を修正しています。

元の変更は、gput 関数(ゴルーチンをキューに戻す関数)内で、ロックされたゴルーチン(gp->lockedm != nil)が runtime.Gosched() を呼び出した際に、そのゴルーチンがすぐに同じMによって再実行されるのを防ぐためのロジックを追加していました。具体的には、ロックされたゴルーチンが gput に渡された場合、そのゴルーチンを別のMに渡すか、またはアイドル状態のMに渡すことで、進行を保証しようとしました。

このコミットでは、その追加されたロジックが削除されています。

削除されたコードは以下の通りです。

	M *mp;

	// If g is wired, hand it off directly.
	if((mp = gp->lockedm) != nil && canaddmcpu()) {
		mnextg(mp, gp);
		return;
	}

このコードは、ゴルーチン gp がOSスレッドにロックされている(gp->lockedm != nil)場合、かつ新しいMを追加できる場合(canaddmcpu())、そのゴルーチンを直接そのロックされたM(mp)に渡して実行させる(mnextg(mp, gp))ことで、進行を試みていました。しかし、このロジックがWindows環境で問題を引き起こしたため、元に戻されました。

また、src/pkg/runtime/proc_test.go からは、TestYieldProgressTestYieldLockedProgress という2つのテスト関数が削除されています。これらのテストは、runtime.Gosched() が通常のゴルーチンとロックされたゴルーチンの両方で進行を保証するかどうかを検証するためのものでした。元の変更が取り消されたため、これらのテストも不要となり削除されました。

この変更は、Goランタイムのスケジューラが、ロックされたゴルーチンと runtime.Gosched() の相互作用をどのように扱うかという複雑な問題を示しています。OSスレッドに固定されたゴルーチンは、スケジューラの自由度を制限するため、デッドロックや進行の停滞を避けるための特別な考慮が必要です。元の修正は、この問題に対処しようとしましたが、プラットフォーム固有の挙動により、意図しない副作用が生じました。

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

src/pkg/runtime/proc.c

--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -397,6 +397,14 @@ canaddmcpu(void)\n static void\n gput(G *gp)\n {\n+\tM *mp;\n+\n+\t// If g is wired, hand it off directly.\n+\tif((mp = gp->lockedm) != nil && canaddmcpu()) {\n+\t\tmnextg(mp, gp);\n+\t\treturn;\n+\t}\n+\n \t// If g is the idle goroutine for an m, hand it off.\n \tif(gp->idlem != nil) {\n \t\tif(gp->idlem->idleg != nil) {\

この差分は、gput 関数に8行のコードが追加されたことを示しています。これは、元のコミットで追加され、このコミットで削除されたコードです。

src/pkg/runtime/proc_test.go

--- a/src/pkg/runtime/proc_test.go
+++ b/src/pkg/runtime/proc_test.go
@@ -46,36 +46,6 @@ func TestStopTheWorldDeadlock(t *testing.T) {\n \truntime.GOMAXPROCS(maxprocs)\n }\n \n-func TestYieldProgress(t *testing.T) {\n-\ttestYieldProgress(t, false)\n-}\n-\n-func TestYieldLockedProgress(t *testing.T) {\n-\ttestYieldProgress(t, true)\n-}\n-\n-func testYieldProgress(t *testing.T, locked bool) {\n-\tc := make(chan bool)\n-\tcack := make(chan bool)\n-\tgo func() {\n-\t\tif locked {\n-\t\t\truntime.LockOSThread()\n-\t\t}\n-\t\tfor {\n-\t\t\tselect {\n-\t\t\tcase <-c:\n-\t\t\t\tcack <- true\n-\t\t\t\tbreak\n-\t\t\tdefault:\n-\t\t\t\truntime.Gosched()\n-\t\t\t}\n-\t\t}\n-\t}()\n-\ttime.Sleep(10 * time.Millisecond)\n-\tc <- true\n-\t<-cack\n-}\n-\n func TestYieldLocked(t *testing.T) {\n \tconst N = 10\n \tc := make(chan bool)\n```

この差分は、`TestYieldProgress` と `TestYieldLockedProgress` という2つのテスト関数、およびそれらが呼び出すヘルパー関数 `testYieldProgress` が完全に削除されたことを示しています。合計30行の削除です。

## コアとなるコードの解説

このコミットのコアとなる変更は、`src/pkg/runtime/proc.c` の `gput` 関数から、ロックされたゴルーチンの進行を保証しようとしたロジックを削除した点です。

`gput` 関数は、ゴルーチンが実行を一時停止し、スケジューラによって再度実行可能になるためにキューに戻される際に呼び出されます。元の変更では、この関数内で、もしゴルーチンがOSスレッドにロックされている(`gp->lockedm != nil`)場合、そのゴルーチンを直接そのロックされたMに渡すことで、他のゴルーチンにCPUを譲った後も、そのゴルーチンがすぐに再開されるように試みていました。これは、ロックされたゴルーチンが `runtime.Gosched()` を呼び出した際に、同じMがそのゴルーチンを繰り返し実行し、他のゴルーチンにCPUを譲らないという問題(Issue #4820)を解決するための試みでした。

しかし、このロジックがWindows環境で問題を引き起こしたため、このコミットではそのロジックが削除されました。これにより、`gput` 関数は、ロックされたゴルーチンに対しても、通常のゴルーチンと同様のパスで処理されることになります。これは、WindowsのOSスレッドのスケジューリングや、GoランタイムがOSスレッドをどのように管理するかの違いに起因する可能性があります。

テストコードの削除は、この変更が元の問題解決の試みを完全に巻き戻したことを意味します。つまり、Issue #4820で報告された問題は、このコミットによって再び未解決の状態に戻されたことになります。これは、Windowsビルドの安定性を優先し、元の問題の解決は別の、より堅牢な方法で行われるべきであるという判断が下されたことを示唆しています。

## 関連リンク

*   Go Issue #4820: [https://github.com/golang/go/issues/4820](https://github.com/golang/go/issues/4820)
*   元の変更 (CL 7310096): [https://golang.org/cl/7310096](https://golang.org/cl/7310096)
*   このコミット (CL 7343050): [https://golang.org/cl/7343050](https://golang.org/cl/7343050)

## 参考にした情報源リンク

*   Go言語の公式ドキュメント (runtimeパッケージ): [https://pkg.go.dev/runtime](https://pkg.go.dev/runtime)
*   Goスケジューラに関するブログ記事やドキュメント (一般的な情報源):
    *   The Go scheduler: [https://go.dev/blog/go15scheduler](https://go.dev/blog/go15scheduler)
    *   Go's work-stealing scheduler: [https://www.ardanlabs.com/blog/2018/08/go-scheduler.html](https://www.ardanlabs.com/blog/2018/08/go-scheduler.html)
*   GitHubのGoリポジトリ: [https://github.com/golang/go](https://github.com/golang/go)
*   Goのコードレビューシステム (Gerrit): [https://go-review.googlesource.com/](https://go-review.googlesource.com/)
*   GoのIssueトラッカー: [https://github.com/golang/go/issues](https://github.com/golang/go/issues)
*   Go言語のソースコード (特に `src/runtime` ディレクトリ): [https://github.com/golang/go/tree/master/src/runtime](https://github.com/golang/go/tree/master/src/runtime)
*   Windows OSのスケジューリングに関する一般的な情報 (必要に応じて): Microsoft Learnなど