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

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

このコミットは、Go言語のreflectパッケージにSelect関数を追加するものです。これにより、Goの組み込みselectステートメントと同様の機能が、リフレクションを通じて動的にチャネル操作を実行できるようになります。具体的には、複数のチャネル操作(送受信、デフォルトケース)を動的に構築し、その中から準備ができたケースを選択して実行する機能が提供されます。この変更は、reflectパッケージのvalue.gotype.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関数の追加は、以下のような背景から必要とされました。

  1. 動的なチャネル操作の必要性: 実行時にチャネルの型や数が決定されるような、より高度な並行処理パターンを構築するためには、selectステートメントの機能をリフレクションを通じて利用できる必要がありました。
  2. 汎用的な並行処理ライブラリの構築: reflect.Selectは、Goの並行処理機能をより汎用的に扱うための基盤を提供し、例えば、動的なワーカープールやイベントディスパッチャなどの構築を容易にします。
  3. 既存の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)を返します。

ランタイムとの連携 (runtimeSelectrselect)

reflect.Selectは、Goのランタイムに存在する低レベルなselect実装(runtime/chan.c内のselectgo関数など)と連携して動作します。この連携のために、reflectパッケージとランタイムの間で共有されるデータ構造が定義されています。

  • runtimeSelect構造体: reflect/value.goruntime/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ケースにChanSend値が指定されている場合。
  • 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

このファイルにSelectDirSelectCase、そして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.goSelect関数

  1. runtimeSelectスライスの準備: Select関数は、入力された[]SelectCaseを、ランタイムが理解できる[]runtimeSelectに変換します。この際、各SelectCaseDirChanSendフィールドを対応するruntimeSelectのフィールドにマッピングします。
  2. バリデーション: 各SelectCaseに対して、その方向(Dir)が有効であるか、ChanSendの値が適切であるかなどの厳密なチェックを行います。例えば、SelectDefaultケースにチャネルが指定されていないか、送信専用チャネルで受信操作が試みられていないかなどを確認します。不正な場合はpanicを発生させます。
  3. rselectの呼び出し: 準備されたruntimeSelectスライスを引数として、ランタイムのrselect関数を呼び出します。このrselectが実際のselectロジックを実行し、選択されたケースのインデックス、受信された値の内部表現(word)、および受信が成功したか(recvOK)を返します。
  4. 結果の変換: rselectから返された結果(chosen, word, recvOK)を、reflect.Selectの戻り値の型(chosen int, recv Value, recvOK bool)に変換します。特に、受信操作の場合、wordから適切なreflect.Valueを再構築します。

runtime/chan.creflect·rselect関数

  1. runtimeSelectの処理: reflect·rselectは、reflect.Selectから渡されたruntimeSelectのスライスを受け取ります。
  2. 受信バッファの確保: 受信操作がある場合、受信する値の最大サイズに基づいて一時的なバッファ(recvptr)を確保します。これは、reflect.Valueが様々な型の値を保持できるため、最も大きな値に対応できるようにするためです。
  3. select構造体の構築: Goランタイムの内部的なSelect構造体(sel)を初期化し、各runtimeSelectに対応するselectsendselectrecvselectdefaultなどのランタイム関数を呼び出して、select操作の準備をします。これらの関数は、チャネル、値、および選択された場合に実行されるコールバックなどを設定します。
  4. selectgoの実行: 最終的に、Goランタイムのコアなselectロジックであるselectgo関数を呼び出します。selectgoは、準備ができたチャネル操作を待機し、選択された操作のインデックスを返します。
  5. 結果の返却: selectgoから返されたインデックスと、受信操作の場合に受信された値(word)およびrecvOKを、reflect.Selectに返します。

この連携により、Goのreflectパッケージは、Goランタイムの低レベルなチャネル操作機能を、型安全性を保ちつつ動的に利用できるようになります。

関連リンク

参考にした情報源リンク

  • Goのソースコード (特にsrc/pkg/reflectsrc/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.gotype.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関数の追加は、以下のような背景から必要とされました。

  1. 動的なチャネル操作の必要性: 実行時にチャネルの型や数が決定されるような、より高度な並行処理パターンを構築するためには、selectステートメントの機能をリフレクションを通じて利用できる必要がありました。
  2. 汎用的な並行処理ライブラリの構築: reflect.Selectは、Goの並行処理機能をより汎用的に扱うための基盤を提供し、例えば、動的なワーカープールやイベントディスパッチャなどの構築を容易にします。
  3. 既存の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)を返します。

ランタイムとの連携 (runtimeSelectrselect)

reflect.Selectは、Goのランタイムに存在する低レベルなselect実装(runtime/chan.c内のselectgo関数など)と連携して動作します。この連携のために、reflectパッケージとランタイムの間で共有されるデータ構造が定義されています。

  • runtimeSelect構造体: reflect/value.goruntime/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ケースにChanSend値が指定されている場合。
  • 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

このファイルにSelectDirSelectCase、そして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.goSelect関数

  1. runtimeSelectスライスの準備: Select関数は、入力された[]SelectCaseを、ランタイムが理解できる[]runtimeSelectに変換します。この際、各SelectCaseDirChanSendフィールドを対応するruntimeSelectのフィールドにマッピングします。
  2. バリデーション: 各SelectCaseに対して、その方向(Dir)が有効であるか、ChanSendの値が適切であるかなどの厳密なチェックを行います。例えば、SelectDefaultケースにチャネルが指定されていないか、送信専用チャネルで受信操作が試みられていないかなどを確認します。不正な場合はpanicを発生させます。
  3. rselectの呼び出し: 準備されたruntimeSelectスライスを引数として、ランタイムのrselect関数を呼び出します。このrselectが実際のselectロジックを実行し、選択されたケースのインデックス、受信された値の内部表現(word)、および受信が成功したか(recvOK)を返します。
  4. 結果の変換: rselectから返された結果(chosen, word, recvOK)を、reflect.Selectの戻り値の型(chosen int, recv Value, recvOK bool)に変換します。特に、受信操作の場合、wordから適切なreflect.Valueを再構築します。

runtime/chan.creflect·rselect関数

  1. runtimeSelectの処理: reflect·rselectは、reflect.Selectから渡されたruntimeSelectのスライスを受け取ります。
  2. 受信バッファの確保: 受信操作がある場合、受信する値の最大サイズに基づいて一時的なバッファ(recvptr)を確保します。これは、reflect.Valueが様々な型の値を保持できるため、最も大きな値に対応できるようにするためです。
  3. select構造体の構築: Goランタイムの内部的なSelect構造体(sel)を初期化し、各runtimeSelectに対応するselectsendselectrecvselectdefaultなどのランタイム関数を呼び出して、select操作の準備をします。これらの関数は、チャネル、値、および選択された場合に実行されるコールバックなどを設定します。
  4. selectgoの実行: 最終的に、Goランタイムのコアなselectロジックであるselectgo関数を呼び出します。selectgoは、準備ができたチャネル操作を待機し、選択された操作のインデックスを返します。
  5. 結果の返却: selectgoから返されたインデックスと、受信操作の場合に受信された値(word)およびrecvOKを、reflect.Selectに返します。

この連携により、Goのreflectパッケージは、Goランタイムの低レベルなチャネル操作機能を、型安全性を保ちつつ動的に利用できるようになります。

関連リンク

参考にした情報源リンク

  • Goのソースコード (特にsrc/pkg/reflectsrc/pkg/runtime/chan.c)
  • Goの公式ドキュメント
  • Goのreflectパッケージに関する一般的な解説記事
  • Goのチャネルとselectステートメントに関する一般的な解説記事
  • コミットメッセージに記載されているGoのコードレビューシステム (Gerrit) のリンク: https://golang.org/cl/6498078 (現在はGitHubのコミットページにリダイレクトされる)
  • Goのunsafeパッケージに関する情報