[インデックス 13858] ファイルの概要
このコミットは、Go言語のreflect
パッケージにSelect
関数を追加するものです。これにより、Goの組み込みselect
ステートメントと同様の機能が、リフレクションを通じて動的にチャネル操作を実行できるようになります。具体的には、複数のチャネル操作(送受信、デフォルトケース)を動的に構築し、その中から準備ができたケースを選択して実行する機能が提供されます。この変更は、reflect
パッケージのvalue.go
、type.go
、テストファイルall_test.go
、そしてGoランタイムのチャネル実装に関連するruntime/chan.c
に影響を与えています。
コミット
commit 370ae055451e0550e6aa8930a12351ea26e96a70
Author: Russ Cox <rsc@golang.org>
Date: Tue Sep 18 14:22:41 2012 -0400
reflect: add Select
R=r, iant, rogpeppe, bradfitz
CC=golang-dev
https://golang.org/cl/6498078
---
src/pkg/reflect/all_test.go | 424 ++++++++++++++++++++++++++++++++++++++++++++
src/pkg/reflect/type.go | 11 +-\n src/pkg/reflect/value.go | 134 ++++++++++++++\n src/pkg/runtime/chan.c | 93 +++++++++-\n 4 files changed, 654 insertions(+), 8 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/370ae055451e0550e6aa8930a12351ea26e96a70
元コミット内容
このコミットは、Go言語の標準ライブラリであるreflect
パッケージにSelect
関数を追加することを目的としています。Select
関数は、Goの組み込みselect
ステートメントの動的なリフレクション版であり、実行時にチャネルの送受信操作をプログラム的に選択することを可能にします。これにより、コンパイル時にチャネルの型や数が確定しないような、より柔軟な並行処理の構築が可能になります。
変更の背景
Go言語のselect
ステートメントは、複数のチャネル操作を待機し、準備ができた最初の操作を実行するための強力な並行処理プリミティブです。しかし、これまでのreflect
パッケージには、select
ステートメントの動的な等価物が存在しませんでした。これにより、例えば、実行時にチャネルのリストが動的に生成されるようなシナリオでは、select
の恩恵を十分に受けることができませんでした。
このSelect
関数の追加は、以下のような背景から必要とされました。
- 動的なチャネル操作の必要性: 実行時にチャネルの型や数が決定されるような、より高度な並行処理パターンを構築するためには、
select
ステートメントの機能をリフレクションを通じて利用できる必要がありました。 - 汎用的な並行処理ライブラリの構築:
reflect.Select
は、Goの並行処理機能をより汎用的に扱うための基盤を提供し、例えば、動的なワーカープールやイベントディスパッチャなどの構築を容易にします。 - 既存の
reflect
パッケージの機能拡張:reflect
パッケージは、Goの型情報を実行時に操作するための強力なツールですが、チャネル操作に関する機能が不足していました。Select
の追加により、reflect
パッケージの機能がより完全なものとなります。
前提知識の解説
このコミットの理解には、以下のGo言語の概念とreflect
パッケージに関する知識が不可欠です。
1. Go言語のチャネル (Channels)
Go言語におけるチャネルは、ゴルーチン間で値を送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送受信できます。
- チャネルの作成:
make(chan int)
(バッファなし),make(chan string, 10)
(バッファあり) - 送信:
ch <- value
- 受信:
value := <-ch
またはvalue, ok := <-ch
(ok
はチャネルが閉じられていないかを示す) - チャネルのクローズ:
close(ch)
2. select
ステートメント
select
ステートメントは、複数のチャネル操作を同時に待機し、準備ができた最初の操作を実行します。これは、非同期I/Oやタイムアウト処理など、複雑な並行処理パターンを実装する際に非常に有用です。
select {
case <-ch1:
// ch1 から値を受信
case ch2 <- value:
// ch2 へ値を送信
case <-time.After(1 * time.Second):
// 1秒後にタイムアウト
default:
// どのチャネルも準備ができていない場合に即座に実行
}
case
句: チャネルの送受信操作を指定します。default
句: どのcase
も準備ができていない場合に即座に実行されます。default
がない場合、select
はチャネル操作が準備できるまでブロックします。
3. reflect
パッケージ
reflect
パッケージは、Goプログラムの実行時に型情報や値情報を検査・操作するための機能を提供します。これにより、ジェネリックな関数やライブラリを構築することが可能になります。
reflect.Type
: Goの型の情報を表します。reflect.Value
: Goの変数の値を表します。reflect.Value
は、その値の型情報(Type
)と、実際の値へのポインタを含みます。ValueOf(interface{}) Value
: 任意のGoの値をreflect.Value
に変換します。Interface() interface{}
:reflect.Value
を元のGoの値に戻します。Kind()
:reflect.Value
が表す値の基本的な種類(例:Chan
,Int
,String
など)を返します。
4. unsafe
パッケージ
unsafe
パッケージは、Goの型安全性をバイパスする低レベルな操作を可能にします。これには、ポインタ演算や、異なる型のポインタ間の変換などが含まれます。reflect
パッケージは、内部的にunsafe
パッケージを使用して、Goのランタイムと密接に連携しています。
技術的詳細
reflect.Select
の追加は、Goのランタイムとreflect
パッケージの間の複雑な連携を伴います。
reflect.Select
のインターフェース
reflect.Select
関数は、[]SelectCase
というスライスを引数に取ります。SelectCase
構造体は、select
ステートメントの各case
句を表現します。
type SelectDir int
const (
_ SelectDir = iota
SelectSend // case Chan <- Send
SelectRecv // case <-Chan:
SelectDefault // default
)
type SelectCase struct {
Dir SelectDir // direction of case
Chan Value // channel to use (for send or receive)
Send Value // value to send (for send)
}
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
SelectDir
:case
の方向(送信、受信、デフォルト)を示します。SelectCase
:Dir
:SelectSend
,SelectRecv
,SelectDefault
のいずれか。Chan
: 操作対象のチャネルを表すreflect.Value
。Send
: 送信操作の場合に送信する値を表すreflect.Value
。受信操作やデフォルトケースではゼロ値である必要があります。
Select
関数は、選択されたcase
のインデックス、受信操作の場合に受信された値(reflect.Value
)、および受信が成功したかどうかを示すブール値(recvOK
)を返します。
ランタイムとの連携 (runtimeSelect
とrselect
)
reflect.Select
は、Goのランタイムに存在する低レベルなselect
実装(runtime/chan.c
内のselectgo
関数など)と連携して動作します。この連携のために、reflect
パッケージとランタイムの間で共有されるデータ構造が定義されています。
-
runtimeSelect
構造体:reflect/value.go
とruntime/chan.c
の両方で定義されており、reflect.Select
がランタイムに渡す各case
の情報を保持します。これには、方向、チャネルの型、チャネルのポインタ、送信する値などが含まれます。// reflect/value.go type runtimeSelect struct { dir uintptr // 0, SendDir, or RecvDir typ *runtimeType // channel type ch iword // interface word for channel val iword // interface word for value (for SendDir) }
// runtime/chan.c typedef struct runtimeSelect runtimeSelect; struct runtimeSelect { uintptr dir; ChanType *typ; Hchan *ch; uintptr val; };
iword
は、reflect.Value
の内部表現の一部であり、値のデータへのポインタまたは直接の値を含みます。 -
rselect
関数:reflect/value.go
で宣言され、runtime/chan.c
で実装されているGo関数です。reflect.Select
は、SelectCase
のスライスをruntimeSelect
のスライスに変換し、このrselect
関数を呼び出すことで、実際のselect
操作をランタイムに委譲します。
型チェックとエラーハンドリング
reflect.Select
は、引数として渡されたSelectCase
スライスに対して厳密な型チェックとバリデーションを行います。
Dir
が不正な場合。SelectDefault
ケースにChan
やSend
値が指定されている場合。SelectSend
ケースでChan
がチャネルでない、または送信専用チャネルに対して受信操作が試みられている場合。SelectRecv
ケースでChan
がチャネルでない、または受信専用チャネルに対して送信操作が試みられている場合。Send
値がチャネルの要素型に割り当て可能でない場合。
これらのチェックは、panic
を引き起こすことで、不正なreflect.Select
の使用を早期に検出します。
テストの追加 (all_test.go
)
このコミットでは、reflect.Select
の動作を網羅的にテストするために、all_test.go
に大量のテストコードが追加されています。特に注目すべきは、exhaustive
というヘルパー構造体と、それを用いた網羅的/確率的テストのフレームワークです。
exhaustive
構造体:Maybe()
やChoose(n)
といったメソッドを提供し、テストケースの生成をランダムかつ網羅的に行います。これにより、reflect.Select
の様々な組み合わせ(準備ができているチャネル、ブロックするチャネル、nilチャネル、クローズされたチャネル、デフォルトケースの有無など)を効率的にテストできます。selectWatcher
:reflect.Select
がデッドロックしないことを監視するためのウォッチドッグメカニズムです。もしselect
が長時間ブロックした場合、エラーを出力してテストをパニックさせます。
コアとなるコードの変更箇所
src/pkg/reflect/value.go
このファイルにSelectDir
、SelectCase
、そしてSelect
関数が追加されています。
// A SelectDir describes the communication direction of a select case.
type SelectDir int
// NOTE: These values must match ../runtime/chan.c:/SelectDir.
const (
_ SelectDir = iota
SelectSend // case Chan <- Send
SelectRecv // case <-Chan:
SelectDefault // default
)
// A SelectCase describes a single case in a select operation.
// ... (コメント省略)
type SelectCase struct {
Dir SelectDir // direction of case
Chan Value // channel to use (for send or receive)
Send Value // value to send (for send)
}
// Select executes a select operation described by the list of cases.
// ... (コメント省略)
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) {
// NOTE: Do not trust that caller is not modifying cases data underfoot.
// The range is safe because the caller cannot modify our copy of the len
// and each iteration makes its own copy of the value c.
runcases := make([]runtimeSelect, len(cases))
haveDefault := false
for i, c := range cases {
rc := &runcases[i]
rc.dir = uintptr(c.Dir)
switch c.Dir {
default:
panic("reflect.Select: invalid Dir")
case SelectDefault: // default
if haveDefault {
panic("reflect.Select: multiple default cases")
}
haveDefault = true
if c.Chan.IsValid() {
panic("reflect.Select: default case has Chan value")
}
if c.Send.IsValid() {
panic("reflect.Select: default case has Send value")
}
case SelectSend:
ch := c.Chan
if !ch.IsValid() {
break // nil Chan, ignored
}
ch.mustBe(Chan)
ch.mustBeExported()
tt := (*chanType)(unsafe.Pointer(ch.typ))
if ChanDir(tt.dir)&SendDir == 0 {
panic("reflect.Select: SendDir case using recv-only channel")
}
rc.ch = ch.iword()
rc.typ = tt.runtimeType()
v := c.Send
if !v.IsValid() {
panic("reflect.Select: SendDir case missing Send value")
}
v.mustBeExported()
v = v.assignTo("reflect.Select", toCommonType(tt.elem), nil)
rc.val = v.iword()
case SelectRecv:
if c.Send.IsValid() {
panic("reflect.Select: RecvDir case has Send value")
}
ch := c.Chan
if !ch.IsValid() {
break // nil Chan, ignored
}
ch.mustBe(Chan)
ch.mustBeExported()
tt := (*chanType)(unsafe.Pointer(ch.typ))
rc.typ = tt.runtimeType()
if ChanDir(tt.dir)&RecvDir == 0 {
panic("reflect.Select: RecvDir case using send-only channel")
}
rc.ch = ch.iword()
}
}
chosen, word, recvOK := rselect(runcases)
if runcases[chosen].dir == uintptr(SelectRecv) {
tt := (*chanType)(unsafe.Pointer(toCommonType(runcases[chosen].typ)))
typ := toCommonType(tt.elem)
fl := flag(typ.Kind()) << flagKindShift
if typ.size > ptrSize {
fl |= flagIndir
}
recv = Value{typ, unsafe.Pointer(word), fl}
}
return chosen, recv, recvOK
}
src/pkg/runtime/chan.c
このファイルにruntimeSelect
構造体のC言語版定義と、reflect.Select
から呼び出されるreflect·rselect
関数が追加されています。
// This struct must match ../reflect/value.go:/runtimeSelect.
typedef struct runtimeSelect runtimeSelect;
struct runtimeSelect
{
uintptr dir;
ChanType *typ;
Hchan *ch;
uintptr val;
};
// This enum must match ../reflect/value.go:/SelectDir.
enum SelectDir {
SelectSend = 1,
SelectRecv,
SelectDefault,
};
// func rselect(cases []runtimeSelect) (chosen int, word uintptr, recvOK bool)
void
reflect·rselect(Slice cases, int32 chosen, uintptr word, bool recvOK)
{
int32 i;
Select *sel;
runtimeSelect* rcase, *rc;
void *elem;
void *recvptr;
uintptr maxsize;
chosen = -1;
word = 0;
recvOK = false;
maxsize = 0;
rcase = (runtimeSelect*)cases.array;
for(i=0; i<cases.len; i++) {
rc = &rcase[i];
if(rc->dir == SelectRecv && rc->ch != nil && maxsize < rc->typ->elem->size)
maxsize = rc->typ->elem->size;
}
recvptr = nil;
if(maxsize > sizeof(void*))
recvptr = runtime·mal(maxsize);
newselect(cases.len, &sel);
for(i=0; i<cases.len; i++) {
rc = &rcase[i];
switch(rc->dir) {
case SelectDefault:
selectdefault(sel, (void*)i, 0);
break;
case SelectSend:
if(rc->ch == nil)
break;
if(rc->typ->elem->size > sizeof(void*))
elem = (void*)rc->val;
else
elem = (void*)&rc->val;
selectsend(sel, rc->ch, (void*)i, elem, 0);
break;
case SelectRecv:
if(rc->ch == nil)
break;
if(rc->typ->elem->size > sizeof(void*))
elem = recvptr;
else
elem = &word;
selectrecv(sel, rc->ch, (void*)i, elem, &recvOK, 0);
break;
}
}
chosen = (int32)(uintptr)selectgo(&sel);
if(rcase[chosen].dir == SelectRecv && rcase[chosen].typ->elem->size > sizeof(void*))
word = (uintptr)recvptr;
FLUSH(&chosen);
FLUSH(&word);
FLUSH(&recvOK);
}
src/pkg/reflect/all_test.go
TestSelect
関数と、exhaustive
構造体、selectWatcher
などが追加され、reflect.Select
の広範なテストが行われています。
src/pkg/reflect/type.go
コメントの移動と、runtimeType
に関するコメントの追加が行われています。これは、reflect
とランタイム間の型情報の共有に関するものです。
コアとなるコードの解説
reflect/value.go
のSelect
関数
runtimeSelect
スライスの準備:Select
関数は、入力された[]SelectCase
を、ランタイムが理解できる[]runtimeSelect
に変換します。この際、各SelectCase
のDir
、Chan
、Send
フィールドを対応するruntimeSelect
のフィールドにマッピングします。- バリデーション: 各
SelectCase
に対して、その方向(Dir
)が有効であるか、Chan
やSend
の値が適切であるかなどの厳密なチェックを行います。例えば、SelectDefault
ケースにチャネルが指定されていないか、送信専用チャネルで受信操作が試みられていないかなどを確認します。不正な場合はpanic
を発生させます。 rselect
の呼び出し: 準備されたruntimeSelect
スライスを引数として、ランタイムのrselect
関数を呼び出します。このrselect
が実際のselect
ロジックを実行し、選択されたケースのインデックス、受信された値の内部表現(word
)、および受信が成功したか(recvOK
)を返します。- 結果の変換:
rselect
から返された結果(chosen
,word
,recvOK
)を、reflect.Select
の戻り値の型(chosen int
,recv Value
,recvOK bool
)に変換します。特に、受信操作の場合、word
から適切なreflect.Value
を再構築します。
runtime/chan.c
のreflect·rselect
関数
runtimeSelect
の処理:reflect·rselect
は、reflect.Select
から渡されたruntimeSelect
のスライスを受け取ります。- 受信バッファの確保: 受信操作がある場合、受信する値の最大サイズに基づいて一時的なバッファ(
recvptr
)を確保します。これは、reflect.Value
が様々な型の値を保持できるため、最も大きな値に対応できるようにするためです。 select
構造体の構築: Goランタイムの内部的なSelect
構造体(sel
)を初期化し、各runtimeSelect
に対応するselectsend
、selectrecv
、selectdefault
などのランタイム関数を呼び出して、select
操作の準備をします。これらの関数は、チャネル、値、および選択された場合に実行されるコールバックなどを設定します。selectgo
の実行: 最終的に、Goランタイムのコアなselect
ロジックであるselectgo
関数を呼び出します。selectgo
は、準備ができたチャネル操作を待機し、選択された操作のインデックスを返します。- 結果の返却:
selectgo
から返されたインデックスと、受信操作の場合に受信された値(word
)およびrecvOK
を、reflect.Select
に返します。
この連携により、Goのreflect
パッケージは、Goランタイムの低レベルなチャネル操作機能を、型安全性を保ちつつ動的に利用できるようになります。
関連リンク
- Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語のチャネルに関する公式ドキュメント: https://go.dev/tour/concurrency/2
- Go言語の
select
ステートメントに関する公式ドキュメント: https://go.dev/tour/concurrency/5
参考にした情報源リンク
- Goのソースコード (特に
src/pkg/reflect
とsrc/pkg/runtime/chan.c
) - Goの公式ドキュメント
- Goの
reflect
パッケージに関する一般的な解説記事 - Goのチャネルと
select
ステートメントに関する一般的な解説記事 - コミットメッセージに記載されているGoのコードレビューシステム (Gerrit) のリンク:
https://golang.org/cl/6498078
(現在はGitHubのコミットページにリダイレクトされる) - Goの
unsafe
パッケージに関する情報# [インデックス 13858] ファイルの概要
このコミットは、Go言語のreflect
パッケージにSelect
関数を追加するものです。これにより、Goの組み込みselect
ステートメントと同様の機能が、リフレクションを通じて動的にチャネル操作を実行できるようになります。具体的には、複数のチャネル操作(送受信、デフォルトケース)を動的に構築し、その中から準備ができたケースを選択して実行する機能が提供されます。この変更は、reflect
パッケージのvalue.go
、type.go
、テストファイルall_test.go
、そしてGoランタイムのチャネル実装に関連するruntime/chan.c
に影響を与えています。
コミット
commit 370ae055451e0550e6aa8930a12351ea26e96a70
Author: Russ Cox <rsc@golang.org>
Date: Tue Sep 18 14:22:41 2012 -0400
reflect: add Select
R=r, iant, rogpeppe, bradfitz
CC=golang-dev
https://golang.org/cl/6498078
---
src/pkg/reflect/all_test.go | 424 ++++++++++++++++++++++++++++++++++++++++++++
src/pkg/reflect/type.go | 11 +-\n src/pkg/reflect/value.go | 134 ++++++++++++++\n src/pkg/runtime/chan.c | 93 +++++++++-\n 4 files changed, 654 insertions(+), 8 deletions(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/370ae055451e0550e6aa8930a12351ea26e96a70
元コミット内容
このコミットは、Go言語の標準ライブラリであるreflect
パッケージにSelect
関数を追加することを目的としています。Select
関数は、Goの組み込みselect
ステートメントの動的なリフレクション版であり、実行時にチャネルの送受信操作をプログラム的に選択することを可能にします。これにより、コンパイル時にチャネルの型や数が確定しないような、より柔軟な並行処理の構築が可能になります。
変更の背景
Go言語のselect
ステートメントは、複数のチャネル操作を待機し、準備ができた最初の操作を実行するための強力な並行処理プリミティブです。しかし、これまでのreflect
パッケージには、select
ステートメントの動的な等価物が存在しませんでした。これにより、例えば、実行時にチャネルのリストが動的に生成されるようなシナリオでは、select
の恩恵を十分に受けることができませんでした。
このSelect
関数の追加は、以下のような背景から必要とされました。
- 動的なチャネル操作の必要性: 実行時にチャネルの型や数が決定されるような、より高度な並行処理パターンを構築するためには、
select
ステートメントの機能をリフレクションを通じて利用できる必要がありました。 - 汎用的な並行処理ライブラリの構築:
reflect.Select
は、Goの並行処理機能をより汎用的に扱うための基盤を提供し、例えば、動的なワーカープールやイベントディスパッチャなどの構築を容易にします。 - 既存の
reflect
パッケージの機能拡張:reflect
パッケージは、Goの型情報を実行時に操作するための強力なツールですが、チャネル操作に関する機能が不足していました。Select
の追加により、reflect
パッケージの機能がより完全なものとなります。
前提知識の解説
このコミットの理解には、以下のGo言語の概念とreflect
パッケージに関する知識が不可欠です。
1. Go言語のチャネル (Channels)
Go言語におけるチャネルは、ゴルーチン間で値を送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送受信できます。
- チャネルの作成:
make(chan int)
(バッファなし),make(chan string, 10)
(バッファあり) - 送信:
ch <- value
- 受信:
value := <-ch
またはvalue, ok := <-ch
(ok
はチャネルが閉じられていないかを示す) - チャネルのクローズ:
close(ch)
2. select
ステートメント
select
ステートメントは、複数のチャネル操作を同時に待機し、準備ができた最初の操作を実行します。これは、非同期I/Oやタイムアウト処理など、複雑な並行処理パターンを実装する際に非常に有用です。
select {
case <-ch1:
// ch1 から値を受信
case ch2 <- value:
// ch2 へ値を送信
case <-time.After(1 * time.Second):
// 1秒後にタイムアウト
default:
// どのチャネルも準備ができていない場合に即座に実行
}
case
句: チャネルの送受信操作を指定します。default
句: どのcase
も準備ができていない場合に即座に実行されます。default
がない場合、select
はチャネル操作が準備できるまでブロックします。
3. reflect
パッケージ
reflect
パッケージは、Goプログラムの実行時に型情報や値情報を検査・操作するための機能を提供します。これにより、ジェネリックな関数やライブラリを構築することが可能になります。
reflect.Type
: Goの型の情報を表します。reflect.Value
: Goの変数の値を表します。reflect.Value
は、その値の型情報(Type
)と、実際の値へのポインタを含みます。ValueOf(interface{}) Value
: 任意のGoの値をreflect.Value
に変換します。Interface() interface{}
:reflect.Value
を元のGoの値に戻します。Kind()
:reflect.Value
が表す値の基本的な種類(例:Chan
,Int
,String
など)を返します。
4. unsafe
パッケージ
unsafe
パッケージは、Goの型安全性をバイパスする低レベルな操作を可能にします。これには、ポインタ演算や、異なる型のポインタ間の変換などが含まれます。reflect
パッケージは、内部的にunsafe
パッケージを使用して、Goのランタイムと密接に連携しています。
技術的詳細
reflect.Select
の追加は、Goのランタイムとreflect
パッケージの間の複雑な連携を伴います。
reflect.Select
のインターフェース
reflect.Select
関数は、[]SelectCase
というスライスを引数に取ります。SelectCase
構造体は、select
ステートメントの各case
句を表現します。
type SelectDir int
const (
_ SelectDir = iota
SelectSend // case Chan <- Send
SelectRecv // case <-Chan:
SelectDefault // default
)
type SelectCase struct {
Dir SelectDir // direction of case
Chan Value // channel to use (for send or receive)
Send Value // value to send (for send)
}
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool)
SelectDir
:case
の方向(送信、受信、デフォルト)を示します。SelectCase
:Dir
:SelectSend
,SelectRecv
,SelectDefault
のいずれか。Chan
: 操作対象のチャネルを表すreflect.Value
。Send
: 送信操作の場合に送信する値を表すreflect.Value
。受信操作やデフォルトケースではゼロ値である必要があります。
Select
関数は、選択されたcase
のインデックス、受信操作の場合に受信された値(reflect.Value
)、および受信が成功したかどうかを示すブール値(recvOK
)を返します。
ランタイムとの連携 (runtimeSelect
とrselect
)
reflect.Select
は、Goのランタイムに存在する低レベルなselect
実装(runtime/chan.c
内のselectgo
関数など)と連携して動作します。この連携のために、reflect
パッケージとランタイムの間で共有されるデータ構造が定義されています。
-
runtimeSelect
構造体:reflect/value.go
とruntime/chan.c
の両方で定義されており、reflect.Select
がランタイムに渡す各case
の情報を保持します。これには、方向、チャネルの型、チャネルのポインタ、送信する値などが含まれます。// reflect/value.go type runtimeSelect struct { dir uintptr // 0, SendDir, or RecvDir typ *runtimeType // channel type ch iword // interface word for channel val iword // interface word for value (for SendDir) }
// runtime/chan.c typedef struct runtimeSelect runtimeSelect; struct runtimeSelect { uintptr dir; ChanType *typ; Hchan *ch; uintptr val; };
iword
は、reflect.Value
の内部表現の一部であり、値のデータへのポインタまたは直接の値を含みます。 -
rselect
関数:reflect/value.go
で宣言され、runtime/chan.c
で実装されているGo関数です。reflect.Select
は、SelectCase
のスライスをruntimeSelect
のスライスに変換し、このrselect
関数を呼び出すことで、実際のselect
操作をランタイムに委譲します。
型チェックとエラーハンドリング
reflect.Select
は、引数として渡されたSelectCase
スライスに対して厳密な型チェックとバリデーションを行います。
Dir
が不正な場合。SelectDefault
ケースにChan
やSend
値が指定されている場合。SelectSend
ケースでChan
がチャネルでない、または送信専用チャネルに対して受信操作が試みられている場合。SelectRecv
ケースでChan
がチャネルでない、または受信専用チャネルに対して送信操作が試みられている場合。Send
値がチャネルの要素型に割り当て可能でない場合。
これらのチェックは、panic
を引き起こすことで、不正なreflect.Select
の使用を早期に検出します。
テストの追加 (all_test.go
)
このコミットでは、reflect.Select
の動作を網羅的にテストするために、all_test.go
に大量のテストコードが追加されています。特に注目すべきは、exhaustive
というヘルパー構造体と、それを用いた網羅的/確率的テストのフレームワークです。
exhaustive
構造体:Maybe()
やChoose(n)
といったメソッドを提供し、テストケースの生成をランダムかつ網羅的に行います。これにより、reflect.Select
の様々な組み合わせ(準備ができているチャネル、ブロックするチャネル、nilチャネル、クローズされたチャネル、デフォルトケースの有無など)を効率的にテストできます。selectWatcher
:reflect.Select
がデッドロックしないことを監視するためのウォッチドッグメカニズムです。もしselect
が長時間ブロックした場合、エラーを出力してテストをパニックさせます。
コアとなるコードの変更箇所
src/pkg/reflect/value.go
このファイルにSelectDir
、SelectCase
、そしてSelect
関数が追加されています。
// A SelectDir describes the communication direction of a select case.
type SelectDir int
// NOTE: These values must match ../runtime/chan.c:/SelectDir.
const (
_ SelectDir = iota
SelectSend // case Chan <- Send
SelectRecv // case <-Chan:
SelectDefault // default
)
// A SelectCase describes a single case in a select operation.
// ... (コメント省略)
type SelectCase struct {
Dir SelectDir // direction of case
Chan Value // channel to use (for send or receive)
Send Value // value to send (for send)
}
// Select executes a select operation described by the list of cases.
// ... (コメント省略)
func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) {
// NOTE: Do not trust that caller is not modifying cases data underfoot.
// The range is safe because the caller cannot modify our copy of the len
// and each iteration makes its own copy of the value c.
runcases := make([]runtimeSelect, len(cases))
haveDefault := false
for i, c := range cases {
rc := &runcases[i]
rc.dir = uintptr(c.Dir)
switch c.Dir {
default:
panic("reflect.Select: invalid Dir")
case SelectDefault: // default
if haveDefault {
panic("reflect.Select: multiple default cases")
}
haveDefault = true
if c.Chan.IsValid() {
panic("reflect.Select: default case has Chan value")
}
if c.Send.IsValid() {
panic("reflect.Select: default case has Send value")
}
case SelectSend:
ch := c.Chan
if !ch.IsValid() {
break // nil Chan, ignored
}
ch.mustBe(Chan)
ch.mustBeExported()
tt := (*chanType)(unsafe.Pointer(ch.typ))
if ChanDir(tt.dir)&SendDir == 0 {
panic("reflect.Select: SendDir case using recv-only channel")
}
rc.ch = ch.iword()
rc.typ = tt.runtimeType()
v := c.Send
if !v.IsValid() {
panic("reflect.Select: SendDir case missing Send value")
}
v.mustBeExported()
v = v.assignTo("reflect.Select", toCommonType(tt.elem), nil)
rc.val = v.iword()
case SelectRecv:
if c.Send.IsValid() {
panic("reflect.Select: RecvDir case has Send value")
}
ch := c.Chan
if !ch.IsValid() {
break // nil Chan, ignored
}
ch.mustBe(Chan)
ch.mustBeExported()
tt := (*chanType)(unsafe.Pointer(ch.typ))
rc.typ = tt.runtimeType()
if ChanDir(tt.dir)&RecvDir == 0 {
panic("reflect.Select: RecvDir case using send-only channel")
}
rc.ch = ch.iword()
}
}
chosen, word, recvOK := rselect(runcases)
if runcases[chosen].dir == uintptr(SelectRecv) {
tt := (*chanType)(unsafe.Pointer(toCommonType(runcases[chosen].typ)))
typ := toCommonType(tt.elem)
fl := flag(typ.Kind()) << flagKindShift
if typ.size > ptrSize {
fl |= flagIndir
}
recv = Value{typ, unsafe.Pointer(word), fl}
}
return chosen, recv, recvOK
}
src/pkg/runtime/chan.c
このファイルにruntimeSelect
構造体のC言語版定義と、reflect.Select
から呼び出されるreflect·rselect
関数が追加されています。
// This struct must match ../reflect/value.go:/runtimeSelect.
typedef struct runtimeSelect runtimeSelect;
struct runtimeSelect
{
uintptr dir;
ChanType *typ;
Hchan *ch;
uintptr val;
};
// This enum must match ../reflect/value.go:/SelectDir.
enum SelectDir {
SelectSend = 1,
SelectRecv,
SelectDefault,
};
// func rselect(cases []runtimeSelect) (chosen int, word uintptr, recvOK bool)
void
reflect·rselect(Slice cases, int32 chosen, uintptr word, bool recvOK)
{
int32 i;
Select *sel;
runtimeSelect* rcase, *rc;
void *elem;
void *recvptr;
uintptr maxsize;
chosen = -1;
word = 0;
recvOK = false;
maxsize = 0;
rcase = (runtimeSelect*)cases.array;
for(i=0; i<cases.len; i++) {
rc = &rcase[i];
if(rc->dir == SelectRecv && rc->ch != nil && maxsize < rc->typ->elem->size)
maxsize = rc->typ->elem->size;
}
recvptr = nil;
if(maxsize > sizeof(void*))
recvptr = runtime·mal(maxsize);
newselect(cases.len, &sel);
for(i=0; i<cases.len; i++) {
rc = &rcase[i];
switch(rc->dir) {
case SelectDefault:
selectdefault(sel, (void*)i, 0);
break;
case SelectSend:
if(rc->ch == nil)
break;
if(rc->typ->elem->size > sizeof(void*))
elem = (void*)rc->val;
else
elem = (void*)&rc->val;
selectsend(sel, rc->ch, (void*)i, elem, 0);
break;
case SelectRecv:
if(rc->ch == nil)
break;
if(rc->typ->elem->size > sizeof(void*))
elem = recvptr;
else
elem = &word;
selectrecv(sel, rc->ch, (void*)i, elem, &recvOK, 0);
break;
}
}
chosen = (int32)(uintptr)selectgo(&sel);
if(rcase[chosen].dir == SelectRecv && rcase[chosen].typ->elem->size > sizeof(void*))
word = (uintptr)recvptr;
FLUSH(&chosen);
FLUSH(&word);
FLUSH(&recvOK);
}
src/pkg/reflect/all_test.go
TestSelect
関数と、exhaustive
構造体、selectWatcher
などが追加され、reflect.Select
の広範なテストが行われています。
src/pkg/reflect/type.go
コメントの移動と、runtimeType
に関するコメントの追加が行われています。これは、reflect
とランタイム間の型情報の共有に関するものです。
コアとなるコードの解説
reflect/value.go
のSelect
関数
runtimeSelect
スライスの準備:Select
関数は、入力された[]SelectCase
を、ランタイムが理解できる[]runtimeSelect
に変換します。この際、各SelectCase
のDir
、Chan
、Send
フィールドを対応するruntimeSelect
のフィールドにマッピングします。- バリデーション: 各
SelectCase
に対して、その方向(Dir
)が有効であるか、Chan
やSend
の値が適切であるかなどの厳密なチェックを行います。例えば、SelectDefault
ケースにチャネルが指定されていないか、送信専用チャネルで受信操作が試みられていないかなどを確認します。不正な場合はpanic
を発生させます。 rselect
の呼び出し: 準備されたruntimeSelect
スライスを引数として、ランタイムのrselect
関数を呼び出します。このrselect
が実際のselect
ロジックを実行し、選択されたケースのインデックス、受信された値の内部表現(word
)、および受信が成功したか(recvOK
)を返します。- 結果の変換:
rselect
から返された結果(chosen
,word
,recvOK
)を、reflect.Select
の戻り値の型(chosen int
,recv Value
,recvOK bool
)に変換します。特に、受信操作の場合、word
から適切なreflect.Value
を再構築します。
runtime/chan.c
のreflect·rselect
関数
runtimeSelect
の処理:reflect·rselect
は、reflect.Select
から渡されたruntimeSelect
のスライスを受け取ります。- 受信バッファの確保: 受信操作がある場合、受信する値の最大サイズに基づいて一時的なバッファ(
recvptr
)を確保します。これは、reflect.Value
が様々な型の値を保持できるため、最も大きな値に対応できるようにするためです。 select
構造体の構築: Goランタイムの内部的なSelect
構造体(sel
)を初期化し、各runtimeSelect
に対応するselectsend
、selectrecv
、selectdefault
などのランタイム関数を呼び出して、select
操作の準備をします。これらの関数は、チャネル、値、および選択された場合に実行されるコールバックなどを設定します。selectgo
の実行: 最終的に、Goランタイムのコアなselect
ロジックであるselectgo
関数を呼び出します。selectgo
は、準備ができたチャネル操作を待機し、選択された操作のインデックスを返します。- 結果の返却:
selectgo
から返されたインデックスと、受信操作の場合に受信された値(word
)およびrecvOK
を、reflect.Select
に返します。
この連携により、Goのreflect
パッケージは、Goランタイムの低レベルなチャネル操作機能を、型安全性を保ちつつ動的に利用できるようになります。
関連リンク
- Go言語の
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語のチャネルに関する公式ドキュメント: https://go.dev/tour/concurrency/2
- Go言語の
select
ステートメントに関する公式ドキュメント: https://go.dev/tour/concurrency/5
参考にした情報源リンク
- Goのソースコード (特に
src/pkg/reflect
とsrc/pkg/runtime/chan.c
) - Goの公式ドキュメント
- Goの
reflect
パッケージに関する一般的な解説記事 - Goのチャネルと
select
ステートメントに関する一般的な解説記事 - コミットメッセージに記載されているGoのコードレビューシステム (Gerrit) のリンク:
https://golang.org/cl/6498078
(現在はGitHubのコミットページにリダイレクトされる) - Goの
unsafe
パッケージに関する情報