[インデックス 15253] ファイルの概要
このコミットは、Go言語のランタイムパッケージ内のテストファイル src/pkg/runtime/proc_test.go
に変更を加えています。具体的には、runtime.LockOSThread()
関数の動作を検証するための新しいテストケースが追加されています。
コミット
- コミットハッシュ:
6a828482fa9045f71328fc51c6917ae5ee649e0e
- 作者: Dmitriy Vyukov dvyukov@google.com
- コミット日時: 2013年2月15日 金曜日 00:02:12 +0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6a828482fa9045f71328fc51c6917ae5ee649e0e
元コミット内容
runtime: add more tests for LockOSThread()
Just test some additional paths through the scheduler.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7331044
変更の背景
このコミットの主な目的は、Goランタイムの runtime.LockOSThread()
関数の堅牢性を高めるためのテストカバレッジを増やすことです。特に、スケジューラが LockOSThread()
によってOSスレッドにロックされたGoroutineをどのように扱うか、そしてそのGoroutineが runtime.Gosched()
やチャネル操作によってブロックされたり、OSスレッドから切り離されずに動作し続けるかを確認することが意図されています。
Goのスケジューラは、多数のGoroutineを少数のOSスレッド上で効率的に多重化することで並行処理を実現します。しかし、LockOSThread()
を使用すると、特定のGoroutineが常に同じOSスレッド上で実行されるように「ロック」されます。このような特殊な状況下でのスケジューラの挙動は、デッドロックや予期せぬ動作を防ぐために徹底的にテストされる必要があります。このコミットは、そのための追加のテストパスを導入しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のランタイムと並行処理に関する概念を理解しておく必要があります。
-
Goroutine: Go言語における軽量な実行単位です。OSスレッドよりもはるかに軽量で、数百万個のGoroutineを同時に実行することも可能です。GoroutineはGoランタイムによって管理され、OSスレッドにマッピングされて実行されます。
-
Goスケジューラ (M:N スケジューリング): Goランタイムには独自のスケジューラが組み込まれており、多数のGoroutine (M) を少数のOSスレッド (N) に効率的にマッピングして実行します。このモデルは「M:Nスケジューリング」と呼ばれます。スケジューラは、Goroutineの実行、一時停止、再開、OSスレッドへの割り当てなどを自動的に行います。
-
OSスレッド: オペレーティングシステムが管理する実行単位です。CPUコア上で直接実行されるのはOSスレッドです。GoのGoroutineは、最終的にOSスレッド上で実行されます。
-
runtime.LockOSThread()
: この関数を呼び出すと、現在のGoroutineが、そのGoroutineが現在実行されているOSスレッドに「ロック」されます。つまり、そのGoroutineは、runtime.UnlockOSThread()
が呼び出されるまで、常に同じOSスレッド上で実行され続けることが保証されます。これは、特定のOSスレッドのプロパティ(例: Cライブラリの特定のスレッドローカルストレージ、GUIイベントループなど)に依存する処理を行う場合に必要となることがあります。 -
runtime.UnlockOSThread()
:runtime.LockOSThread()
によってロックされたGoroutineを、OSスレッドから解放します。これにより、そのGoroutineは再びGoスケジューラの管理下に戻り、任意のOSスレッド上で実行されるようになります。 -
runtime.Gosched()
: この関数を呼び出すと、現在のGoroutineは実行を一時停止し、Goスケジューラに制御を戻します。スケジューラは、他の実行可能なGoroutineにCPUを割り当てます。これは、Goroutineが自発的に協調的マルチタスクを行うためのメカニズムです。 -
チャネル (Channel): Goroutine間で安全にデータを送受信するためのGoのプリミティブです。チャネル操作(送信
c <- value
や受信<-c
)は、データが利用可能になるまでGoroutineをブロックする可能性があります。
これらの概念は、LockOSThread()
が有効なGoroutineが Gosched()
やチャネル操作によってどのように振る舞うかを理解する上で重要です。
技術的詳細
このコミットでは、runtime.LockOSThread()
の動作を検証するために、以下の2つの新しいテスト関数が src/pkg/runtime/proc_test.go
に追加されています。
-
TestYieldLocked
: このテストは、runtime.LockOSThread()
によってOSスレッドにロックされたGoroutineがruntime.Gosched()
を呼び出した場合に、スケジューラがどのように振る舞うかを検証します。- Goroutineは
runtime.LockOSThread()
を呼び出してOSスレッドに自身をロックします。 - ループ内で
runtime.Gosched()
を繰り返し呼び出し、他のGoroutineに実行を譲ります。 time.Sleep(time.Millisecond)
を挟むことで、Goroutineが実際に一時停止し、その後も同じOSスレッド上で実行を再開できることを確認します。- 重要な点: このテストでは、
runtime.UnlockOSThread()
が意図的に呼び出されていません。これは、LockOSThread()
が呼び出されたGoroutineが、明示的にアンロックされなくても、Gosched()
やtime.Sleep
のような操作を安全に実行できることを確認するためです。これにより、ロックされたGoroutineがスケジューラと適切に連携できるか、そしてOSスレッドから切り離されないかを確認します。
- Goroutineは
-
TestBlockLocked
: このテストは、runtime.LockOSThread()
によってOSスレッドにロックされたGoroutineがチャネル操作によってブロックされた場合に、スケジューラがどのように振る舞うかを検証します。- Goroutineは
runtime.LockOSThread()
を呼び出してOSスレッドに自身をロックします。 - ループ内でチャネル
c
にデータを送信 (c <- true
) します。この操作は、受信側が準備できるまでGoroutineをブロックする可能性があります。 - メインGoroutineは、チャネルからデータを繰り返し受信 (
<-c
) します。 - 重要な点: このテストは、ロックされたGoroutineがチャネル操作によってブロックされても、デッドロックに陥ることなく、ブロックが解除された後に同じOSスレッド上で実行を再開できることを確認します。最後に
runtime.UnlockOSThread()
が呼び出され、GoroutineがOSスレッドから解放されることも確認しています。
- Goroutineは
これらのテストは、LockOSThread()
がGoのスケジューリングメカニズムとどのように相互作用するか、特にGoroutineが自発的に実行を譲ったり(Gosched
)、外部イベント(チャネル)を待ったりする場合の挙動を詳細に検証することを目的としています。これにより、LockOSThread()
の使用がランタイムの安定性や予測可能性を損なわないことを保証します。
コアとなるコードの変更箇所
変更は src/pkg/runtime/proc_test.go
ファイルに集中しており、以下のコードブロックが追加されています。
--- a/src/pkg/runtime/proc_test.go
+++ b/src/pkg/runtime/proc_test.go
@@ -8,6 +8,7 @@ import (
"runtime"
"sync/atomic"
"testing"
+ "time"
)
var stop = make(chan bool, 1)
@@ -45,6 +46,36 @@ func TestStopTheWorldDeadlock(t *testing.T) {
runtime.GOMAXPROCS(maxprocs)
}
+func TestYieldLocked(t *testing.T) {
+ const N = 10
+ c := make(chan bool)
+ go func() {
+ runtime.LockOSThread()
+ for i := 0; i < N; i++ {
+ runtime.Gosched()
+ time.Sleep(time.Millisecond)
+ }
+ c <- true
+ // runtime.UnlockOSThread() is deliberately omitted
+ }()
+ <-c
+}
+
+func TestBlockLocked(t *testing.T) {
+ const N = 10
+ c := make(chan bool)
+ go func() {
+ runtime.LockOSThread()
+ for i := 0; i < N; i++ {
+ c <- true
+ }
+ runtime.UnlockOSThread()
+ }()
+ for i := 0; i < N; i++ {
+ <-c
+ }
+}
+
func stackGrowthRecursive(i int) {
var pad [128]uint64
if i != 0 && pad[0] == 0 {
具体的には、import
文に time
パッケージが追加され、TestYieldLocked
と TestBlockLocked
の2つの新しいテスト関数が定義されています。
コアとなるコードの解説
TestYieldLocked
関数
func TestYieldLocked(t *testing.T) {
const N = 10
c := make(chan bool)
go func() {
runtime.LockOSThread() // 現在のGoroutineをOSスレッドにロック
for i := 0; i < N; i++ {
runtime.Gosched() // 実行をスケジューラに譲る
time.Sleep(time.Millisecond) // 短時間スリープ
}
c <- true // 完了を通知
// runtime.UnlockOSThread() is deliberately omitted // 意図的にアンロックしない
}()
<-c // Goroutineの完了を待つ
}
このテストは、runtime.LockOSThread()
でOSスレッドにロックされたGoroutineが、runtime.Gosched()
を呼び出して実行をスケジューラに譲り、さらに time.Sleep()
で一時停止しても、そのGoroutineが引き続き同じOSスレッド上で実行され続けることを確認します。runtime.UnlockOSThread()
が意図的に省略されているのは、ロックされた状態でのスケジューラとの相互作用をより厳密にテストするためです。これにより、ロックされたGoroutineがスケジューラによって誤って別のOSスレッドに移動されたり、デッドロックに陥ったりしないことを保証します。
TestBlockLocked
関数
func TestBlockLocked(t *testing.T) {
const N = 10
c := make(chan bool)
go func() {
runtime.LockOSThread() // 現在のGoroutineをOSスレッドにロック
for i := 0; i < N; i++ {
c <- true // チャネルにデータを送信(ブロックされる可能性あり)
}
runtime.UnlockOSThread() // ロックを解除
}()
for i := 0; i < N; i++ {
<-c // チャネルからデータを受信
}
}
このテストは、runtime.LockOSThread()
でOSスレッドにロックされたGoroutineが、チャネル操作(この場合は送信 c <- true
)によってブロックされた場合に、どのように振る舞うかを検証します。ロックされたGoroutineがブロックされても、デッドロックに陥ることなく、ブロックが解除された後に同じOSスレッド上で実行を再開できることを確認します。テストの最後に runtime.UnlockOSThread()
が呼び出され、GoroutineがOSスレッドから適切に解放されることも確認しています。これは、LockOSThread()
を使用する際の一般的なユースケースにおける堅牢性を保証するために重要です。
関連リンク
- Go CL (Code Review) へのリンク: https://golang.org/cl/7331044
参考にした情報源リンク
- Go言語の公式ドキュメント:
runtime
パッケージ - Go言語のスケジューラに関する記事やブログポスト (例: "Go's work-stealing scheduler", "Go scheduler: M, P, G")
runtime.LockOSThread
の使用例や解説記事
[インデックス 15253] ファイルの概要
このコミットは、Go言語のランタイムパッケージ内のテストファイル src/pkg/runtime/proc_test.go
に変更を加えています。具体的には、runtime.LockOSThread()
関数の動作を検証するための新しいテストケースが追加されています。
コミット
- コミットハッシュ:
6a828482fa9045f71328fc51c6917ae5ee649e0e
- 作者: Dmitriy Vyukov dvyukov@google.com
- コミット日時: 2013年2月15日 金曜日 00:02:12 +0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6a828482fa9045f71328fc51c6917ae5ee649e0e
元コミット内容
runtime: add more tests for LockOSThread()
Just test some additional paths through the scheduler.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7331044
変更の背景
このコミットの主な目的は、Goランタイムの runtime.LockOSThread()
関数の堅牢性を高めるためのテストカバレッジを増やすことです。特に、スケジューラが LockOSThread()
によってOSスレッドにロックされたGoroutineをどのように扱うか、そしてそのGoroutineが runtime.Gosched()
やチャネル操作によってブロックされたり、OSスレッドから切り離されずに動作し続けるかを確認することが意図されています。
Goのスケジューラは、多数のGoroutineを少数のOSスレッド上で効率的に多重化することで並行処理を実現します。しかし、LockOSThread()
を使用すると、特定のGoroutineが常に同じOSスレッド上で実行されるように「ロック」されます。このような特殊な状況下でのスケジューラの挙動は、デッドロックや予期せぬ動作を防ぐために徹底的にテストされる必要があります。このコミットは、そのための追加のテストパスを導入しています。
前提知識の解説
このコミットを理解するためには、以下のGo言語のランタイムと並行処理に関する概念を理解しておく必要があります。
-
Goroutine: Go言語における軽量な実行単位です。OSスレッドよりもはるかに軽量で、数百万個のGoroutineを同時に実行することも可能です。GoroutineはGoランタイムによって管理され、OSスレッドにマッピングされて実行されます。
-
Goスケジューラ (M:N スケジューリング): Goランタイムには独自のスケジューラが組み込まれており、多数のGoroutine (M) を少数のOSスレッド (N) に効率的にマッピングして実行します。このモデルは「M:Nスケジューリング」と呼ばれます。スケジューラは、Goroutineの実行、一時停止、再開、OSスレッドへの割り当てなどを自動的に行います。
-
OSスレッド: オペレーティングシステムが管理する実行単位です。CPUコア上で直接実行されるのはOSスレッドです。GoのGoroutineは、最終的にOSスレッド上で実行されます。
-
runtime.LockOSThread()
: この関数を呼び出すと、現在のGoroutineが、そのGoroutineが現在実行されているOSスレッドに「ロック」されます。つまり、そのGoroutineは、runtime.UnlockOSThread()
が呼び出されるまで、常に同じOSスレッド上で実行され続けることが保証されます。これは、特定のOSスレッドのプロパティ(例: Cライブラリの特定のスレッドローカルストレージ、GUIイベントループなど)に依存する処理を行う場合に必要となることがあります。例えば、GUIフレームワーク(macOSのAppKit、iOSのUIKit、OpenGL、SDLなど)は、その関数が特定の「メイン」スレッドから呼び出されることを要求することが多く、このような場合にruntime.LockOSThread
が使用されます。また、スレッドローカルストレージ(TLS)に依存するCライブラリを使用する場合にも、TLSデータを維持するために同じOSスレッド上でGoroutineが実行され続けることを保証するために利用されます。 -
runtime.UnlockOSThread()
:runtime.LockOSThread()
によってロックされたGoroutineを、OSスレッドから解放します。これにより、そのGoroutineは再びGoスケジューラの管理下に戻り、任意のOSスレッド上で実行されるようになります。通常はLockOSThread
と対で使用され、Goroutineがスレッドをロックしたまま終了すると、そのスレッドは終了します。 -
runtime.Gosched()
: この関数を呼び出すと、現在のGoroutineは実行を一時停止し、Goスケジューラに制御を戻します。スケジューラは、他の実行可能なGoroutineにCPUを割り当てます。これは、Goroutineが自発的に協調的マルチタスクを行うためのメカニズムです。 -
チャネル (Channel): Goroutine間で安全にデータを送受信するためのGoのプリミティブです。チャネル操作(送信
c <- value
や受信<-c
)は、データが利用可能になるまでGoroutineをブロックする可能性があります。
これらの概念は、LockOSThread()
が有効なGoroutineが Gosched()
やチャネル操作によってどのように振る舞うかを理解する上で重要です。
技術的詳細
このコミットでは、runtime.LockOSThread()
の動作を検証するために、以下の2つの新しいテスト関数が src/pkg/runtime/proc_test.go
に追加されています。
-
TestYieldLocked
: このテストは、runtime.LockOSThread()
によってOSスレッドにロックされたGoroutineがruntime.Gosched()
を呼び出した場合に、スケジューラがどのように振る舞うかを検証します。- Goroutineは
runtime.LockOSThread()
を呼び出してOSスレッドに自身をロックします。 - ループ内で
runtime.Gosched()
を繰り返し呼び出し、他のGoroutineに実行を譲ります。 time.Sleep(time.Millisecond)
を挟むことで、Goroutineが実際に一時停止し、その後も同じOSスレッド上で実行を再開できることを確認します。- 重要な点: このテストでは、
runtime.UnlockOSThread()
が意図的に呼び出されていません。これは、LockOSThread()
が呼び出されたGoroutineが、明示的にアンロックされなくても、Gosched()
やtime.Sleep
のような操作を安全に実行できることを確認するためです。これにより、ロックされたGoroutineがスケジューラと適切に連携できるか、そしてOSスレッドから切り離されないかを確認します。
- Goroutineは
-
TestBlockLocked
: このテストは、runtime.LockOSThread()
によってOSスレッドにロックされたGoroutineがチャネル操作によってブロックされた場合に、スケジューラがどのように振る舞うかを検証します。- Goroutineは
runtime.LockOSThread()
を呼び出してOSスレッドに自身をロックします。 - ループ内でチャネル
c
にデータを送信 (c <- true
) します。この操作は、受信側が準備できるまでGoroutineをブロックする可能性があります。 - メインGoroutineは、チャネルからデータを繰り返し受信 (
<-c
) します。 - 重要な点: このテストは、ロックされたGoroutineがチャネル操作によってブロックされても、デッドロックに陥ることなく、ブロックが解除された後に同じOSスレッド上で実行を再開できることを確認します。最後に
runtime.UnlockOSThread()
が呼び出され、GoroutineがOSスレッドから解放されることも確認しています。
- Goroutineは
これらのテストは、LockOSThread()
がGoのスケジューリングメカニズムとどのように相互作用するか、特にGoroutineが自発的に実行を譲ったり(Gosched
)、外部イベント(チャネル)を待ったりする場合の挙動を詳細に検証することを目的としています。これにより、LockOSThread()
の使用がランタイムの安定性や予測可能性を損なわないことを保証します。
コアとなるコードの変更箇所
変更は src/pkg/runtime/proc_test.go
ファイルに集中しており、以下のコードブロックが追加されています。
--- a/src/pkg/runtime/proc_test.go
+++ b/src/pkg/runtime/proc_test.go
@@ -8,6 +8,7 @@ import (
"runtime"
"sync/atomic"
"testing"
+ "time"
)
var stop = make(chan bool, 1)
@@ -45,6 +46,36 @@ func TestStopTheWorldDeadlock(t *testing.T) {
runtime.GOMAXPROCS(maxprocs)
}
+func TestYieldLocked(t *testing.T) {
+ const N = 10
+ c := make(chan bool)
+ go func() {
+ runtime.LockOSThread() // 現在のGoroutineをOSスレッドにロック
+ for i := 0; i < N; i++ {
+ runtime.Gosched() // 実行をスケジューラに譲る
+ time.Sleep(time.Millisecond) // 短時間スリープ
+ }
+ c <- true // 完了を通知
+ // runtime.UnlockOSThread() is deliberately omitted // 意図的にアンロックしない
+ }()
+ <-c // Goroutineの完了を待つ
+}
+
+func TestBlockLocked(t *testing.T) {
+ const N = 10
+ c := make(chan bool)
+ go func() {
+ runtime.LockOSThread() // 現在のGoroutineをOSスレッドにロック
+ for i := 0; i < N; i++ {
+ c <- true // チャネルにデータを送信(ブロックされる可能性あり)
+ }
+ runtime.UnlockOSThread() // ロックを解除
+ }()
+ for i := 0; i < N; i++ {
+ <-c // チャネルからデータを受信
+ }
+}
+
func stackGrowthRecursive(i int) {
var pad [128]uint64
if i != 0 && pad[0] == 0 {
具体的には、import
文に time
パッケージが追加され、TestYieldLocked
と TestBlockLocked
の2つの新しいテスト関数が定義されています。
コアとなるコードの解説
TestYieldLocked
関数
func TestYieldLocked(t *testing.T) {
const N = 10
c := make(chan bool)
go func() {
runtime.LockOSThread() // 現在のGoroutineをOSスレッドにロック
for i := 0; i < N; i++ {
runtime.Gosched() // 実行をスケジューラに譲る
time.Sleep(time.Millisecond) // 短時間スリープ
}
c <- true // 完了を通知
// runtime.UnlockOSThread() is deliberately omitted // 意図的にアンロックしない
}()
<-c // Goroutineの完了を待つ
}
このテストは、runtime.LockOSThread()
でOSスレッドにロックされたGoroutineが、runtime.Gosched()
を呼び出して実行をスケジューラに譲り、さらに time.Sleep()
で一時停止しても、そのGoroutineが引き続き同じOSスレッド上で実行され続けることを確認します。runtime.UnlockOSThread()
が意図的に省略されているのは、ロックされた状態でのスケジューラとの相互作用をより厳密にテストするためです。これにより、ロックされたGoroutineがスケジューラによって誤って別のOSスレッドに移動されたり、デッドロックに陥ったりしないことを保証します。
TestBlockLocked
関数
func TestBlockLocked(t *testing.T) {
const N = 10
c := make(chan bool)
go func() {
runtime.LockOSThread() // 現在のGoroutineをOSスレッドにロック
for i := 0; i < N; i++ {
c <- true // チャネルにデータを送信(ブロックされる可能性あり)
}
runtime.UnlockOSThread() // ロックを解除
}()
for i := 0; i < N; i++ {
<-c // チャネルからデータを受信
}
}
このテストは、runtime.LockOSThread()
でOSスレッドにロックされたGoroutineが、チャネル操作(この場合は送信 c <- true
)によってブロックされた場合に、どのように振る舞うかを検証します。ロックされたGoroutineがブロックされても、デッドロックに陥ることなく、ブロックが解除された後に同じOSスレッド上で実行を再開できることを確認します。テストの最後に runtime.UnlockOSThread()
が呼び出され、GoroutineがOSスレッドから適切に解放されることも確認しています。これは、LockOSThread()
を使用する際の一般的なユースケースにおける堅牢性を保証するために重要です。
関連リンク
- Go CL (Code Review) へのリンク: https://golang.org/cl/7331044
参考にした情報源リンク
- Go言語の公式ドキュメント:
runtime
パッケージ - Go言語のスケジューラに関する記事やブログポスト (例: "Go's work-stealing scheduler", "Go scheduler: M, P, G")
runtime.LockOSThread
の使用例や解説記事- Web検索結果: "Go runtime.LockOSThread"