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

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

このコミットは、Go言語の初期開発段階における重要な構文変更と、並行処理のテストケース追加を含んでいます。具体的には、rangeキーワードを用いたループ構文において、キーと値の区切り文字が:から,へと変更されました。これに伴い、コンパイラの文法定義ファイルが更新され、古い構文のサポートが削除されています。また、Go言語の重要な機能であるチャネルの動作を検証するための新しい並行テストが追加されました。

コミット

commit 7859ae8a2f5f5b48d5961df7c6e84ce7d7c3c46b
Author: Ken Thompson <ken@golang.org>
Date:   Mon Jan 26 11:34:38 2009 -0800

    removed a:b in range syntax
    added another channel test
    
    R=r
    OCL=23488
    CL=23488

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

https://github.com/golang/go/commit/7859ae8a2f5f5b48d5961df7c6e84ce7d7c3c46b

元コミット内容

commit 7859ae8a2f5f5b48d5961df7c6e84ce7d7c3c46b
Author: Ken Thompson <ken@golang.org>
Date:   Mon Jan 26 11:34:38 2009 -0800

    removed a:b in range syntax
    added another channel test
    
    R=r
    OCL=23488
    CL=23488
---
 src/cmd/gc/go.y   | 12 ------------
 test/ken/chan1.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 test/ken/range.go |  8 ++++----
 3 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index e8843a97e6..0a7cd0813b 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -538,23 +538,11 @@ orange_stmt:
  		$$ = nod(ORANGE, $1, $4);
  		$$->etype = 0;	// := flag
  	}
-|\texprsym3 ':' exprsym3 '=' LRANGE expr
-	{\n-\t\t$$ = nod(OLIST, $1, $3);\n-\t\t$$ = nod(ORANGE, $$, $6);\n-\t\t$$->etype = 0;\n-\t}\n |\texprsym3_list_r LCOLAS LRANGE expr
  	{\n \t\t$$ = nod(ORANGE, $1, $4);\n \t\t$$->etype = 1;\n \t}\n-|\texprsym3 ':' exprsym3 LCOLAS LRANGE expr
-	{\n-\t\t$$ = nod(OLIST, $1, $3);\n-\t\t$$ = nod(ORANGE, $$, $6);\n-\t\t$$->etype = 1;\n-\t}\n 
 for_header:
  	osimple_stmt ';' orange_stmt ';' osimple_stmt
diff --git a/test/ken/chan1.go b/test/ken/chan1.go
new file mode 100644
index 0000000000..c6d7825b77
--- /dev/null
+++ b/test/ken/chan1.go
@@ -0,0 +1,56 @@
+// $G $D/$F.go && $L $F.$A && ./$A.out
+
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+const	N	= 1000;		// sent messages
+const	M	= 10;		// receiving goroutines
+const	W	= 2;		// channel buffering
+var	h	[N]int;		// marking of send/recv
+
+func
+r(c chan int, m int)
+{
+	for {
+	       	select {
+		case r := <- c:
+			if h[r] != 1 {
+				panicln("r",
+					"m=", m,
+					"r=", r,
+					"h=", h[r]
+				);
+			}
+			h[r] = 2;
+		}
+        }
+}
+
+func
+s(c chan int)
+{
+	for n:=0; n<N; n++ {
+		r := n;
+		if h[r] != 0 {
+			panicln("s");
+		}
+		h[r] = 1;
+		c <- r;
+	}
+}
+
+func
+main()
+{
+	c := make(chan int, W);
+	for m:=0; m<M; m++ {
+		go r(c, m);
+		sys.Gosched();
+	}
+	sys.Gosched();
+	sys.Gosched();
+	s(c);
+}
diff --git a/test/ken/range.go b/test/ken/range.go
index 2417580ddb..55e168920b 100644
--- a/test/ken/range.go
+++ b/test/ken/range.go
@@ -76,10 +76,10 @@ main()
 	}
 
 	/*
-\t * key:value
+\t * key,value
 	 */
 	i = 0;
-\tfor k:v := range a {\n+\tfor k,v := range a {\n \t\tif v != f(k) {\n \t\t\tpanicln("key:value array range", k, v, a[k]);\n \t\t}\n@@ -90,7 +90,7 @@ main()
 	}
 
 	i = 0;
-\tfor k:v := range p {\n+\tfor k,v := range p {\n \t\tif v != f(k) {\n \t\t\tpanicln("key:value pointer range", k, v, p[k]);\n \t\t}\n@@ -101,7 +101,7 @@ main()
 	}
 
 	i = 0;
-\tfor k:v := range m {\n+\tfor k,v := range m {\n \t\tif v != f(k) {\n \t\t\tpanicln("key:value map range", k, v, m[k]);\n \t\t}\n```

## 変更の背景

このコミットは、Go言語の初期設計段階における言語構文の洗練と、並行処理モデルの堅牢性確保という二つの主要な背景を持っています。

1.  **`range`構文の統一と簡素化**:
    Go言語の`range`キーワードは、配列、スライス、文字列、マップ、チャネルといったコレクションの要素を反復処理するための強力な機能です。初期の設計では、マップや配列のインデックスと値を同時に取得する際に、`for k:v := range collection`のような`key:value`形式の構文が検討されていた可能性があります。しかし、Go言語の設計哲学は「シンプルさ」と「明瞭さ」を重視しており、複数の値を宣言する際の一般的なパターン(例: `x, y := func()`)との一貫性を保つことが重要でした。このコミットは、`range`ループにおけるキーと値のペアの宣言を、他の多値代入と同様にカンマ区切り(`key, value`)に統一することで、言語全体の構文の一貫性を高め、学習コストを削減することを目的としています。

2.  **チャネルテストの拡充**:
    Go言語の並行処理は、ゴルーチンとチャネルを基盤としています。チャネルはゴルーチン間の安全な通信を可能にする重要なプリミティブであり、その正しい動作は言語の信頼性にとって不可欠です。初期の言語開発段階では、チャネルの様々な使用パターンやエッジケースを網羅的にテストすることが求められていました。このコミットで追加された`chan1.go`は、複数のゴルーチンがチャネルを介してデータを送受信するシナリオをシミュレートし、チャネルのバッファリング、`select`ステートメント、および並行アクセスにおけるデータの一貫性を検証するためのものです。これにより、チャネルの実装が意図通りに機能し、競合状態やデッドロックなどの問題が発生しないことを保証しようとしています。

## 前提知識の解説

### Go言語の`range`キーワード

Go言語の`for`ループは、C言語のような伝統的なループ構文に加えて、`range`キーワードを用いたイテレーションをサポートしています。`range`は、配列、スライス、文字列、マップ、チャネルといったデータ構造を反復処理するために使用されます。

*   **配列とスライス**: `for index, value := range arrayOrSlice` の形式で、インデックスと要素の値を取得できます。インデックスのみが必要な場合は `for index := range arrayOrSlice`、値のみが必要な場合は `for _, value := range arrayOrSlice` と書きます。
*   **文字列**: `for index, runeValue := range string` の形式で、UTF-8エンコードされた文字列のバイトインデックスとUnicodeコードポイント(rune)を取得できます。
*   **マップ**: `for key, value := range map` の形式で、キーと値のペアを取得できます。マップのイテレーション順序は保証されません。
*   **チャネル**: `for value := range channel` の形式で、チャネルから値が送信されるたびにそれを受け取ります。チャネルが閉じられるとループは終了します。

このコミット以前は、マップや配列のキーと値の取得に`key:value`という構文が使われていた時期があったことが示唆されています。

### Go言語のチャネルとゴルーチン

*   **ゴルーチン (Goroutines)**: Go言語における軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数千、数万のゴルーチンを同時に実行することが可能です。`go`キーワードを関数の呼び出しの前に置くことで、その関数を新しいゴルーチンとして実行できます。
*   **チャネル (Channels)**: ゴルーチン間で値を安全に送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送信できます。チャネルは、バッファリングされていない(同期)チャネルと、バッファリングされた(非同期)チャネルがあります。
    *   **バッファリングされていないチャネル**: 送信操作は受信操作が完了するまでブロックし、受信操作は送信操作が完了するまでブロックします。これにより、ゴルーチン間の同期が保証されます。
    *   **バッファリングされたチャネル**: 指定された数の値をバッファに保持できます。バッファが満杯になるまで送信操作はブロックせず、バッファが空になるまで受信操作はブロックしません。
*   **`select`ステートメント**: 複数のチャネル操作を同時に待機し、準備ができた最初の操作を実行するために使用されます。これにより、複雑な並行処理のパターンを簡潔に記述できます。

### Yacc/Bison (go.y)

`src/cmd/gc/go.y`ファイルは、Goコンパイラの字句解析器と構文解析器を生成するために使用されるYacc(Yet Another Compiler Compiler)またはBisonの文法定義ファイルです。Yacc/Bisonは、BNF(Backus-Naur Form)に似た形式で文法規則を記述することで、プログラミング言語の構文を定義し、それに対応するパーサーコードを自動生成するツールです。

*   `.y`ファイルには、言語のトークン(終端記号)と非終端記号、そしてそれらの組み合わせによって構成される文法規則が記述されます。
*   各規則には、その規則が認識されたときに実行されるアクション(通常はC言語のコード)を関連付けることができます。このアクションは、抽象構文木(AST)の構築やセマンティックチェックなどを行います。
*   このコミットでは、`go.y`から`range`構文の古い形式(`exprsym3 ':' exprsym3`)に対応する文法規則が削除されています。これは、コンパイラが新しい構文のみを認識するように変更されたことを意味します。

## 技術的詳細

### `range`構文の変更

このコミットの主要な変更点の一つは、Go言語の`range`ループにおけるキーと値のペアの宣言構文が`key:value`から`key,value`に変更されたことです。

*   **変更前(推測される古い構文)**:
    ```go
    for k:v := range collection {
        // ...
    }
    ```
    この構文は、Pythonの辞書イテレーションや他の言語のキーと値のペアを連想させるかもしれませんが、Go言語の多値代入(例: `a, b := someFunc()`)の一般的なパターンとは異なっていました。

*   **変更後(現在のGo言語の構文)**:
    ```go
    for k,v := range collection {
        // ...
    }
    ```
    この変更により、`range`ループでの多値代入が、Go言語の他の場所での多値代入と一貫性を持つようになりました。これは、言語の設計原則である「シンプルさ」と「一貫性」を反映したものです。

`src/cmd/gc/go.y`の変更は、この構文変更をコンパイラレベルで反映しています。具体的には、`orange_stmt`(`range`ステートメントに関連する文法規則)から、`:`を含む古い形式の規則が削除されました。これにより、コンパイラは古い構文をエラーとして扱うようになります。

### 新しいチャネルテスト (`test/ken/chan1.go`)

`test/ken/chan1.go`は、Go言語のチャネルとゴルーチンの堅牢性を検証するために設計された新しいテストケースです。このテストは、以下の要素を組み合わせています。

*   **定数**:
    *   `N = 1000`: 送信するメッセージの総数。
    *   `M = 10`: 受信側のゴルーチンの数。
    *   `W = 2`: チャネルのバッファサイズ。
*   **`h`配列**: `h [N]int`は、各メッセージの送信/受信状態を追跡するための配列です。
    *   `0`: 未送信
    *   `1`: 送信済み(`s`ゴルーチンによって設定)
    *   `2`: 受信済み(`r`ゴルーチンによって設定)
*   **`r`関数 (受信側ゴルーチン)**:
    *   `select`ステートメントを使用して、チャネル`c`からの受信操作を待機します。
    *   値`r`を受信すると、`h[r]`が`1`であることを確認します。もし`1`でなければ、`panicln`を呼び出してエラーを報告します。これは、メッセージが送信される前に受信されたり、複数回受信されたりするなどの異常な状態を検出するためです。
    *   `h[r]`を`2`に設定し、メッセージが正常に受信されたことをマークします。
*   **`s`関数 (送信側ゴルーチン)**:
    *   `0`から`N-1`までの整数をチャネル`c`に送信します。
    *   値を送信する前に、`h[r]`が`0`であることを確認します。もし`0`でなければ、`panicln`を呼び出してエラーを報告します。これは、メッセージが既に送信済みであるなどの異常な状態を検出するためです。
    *   `h[r]`を`1`に設定し、メッセージが送信されたことをマークします。
    *   `c <- r`でチャネルに値を送信します。チャネルがバッファリングされているため、バッファが満杯でない限りブロックしません。
*   **`main`関数**:
    *   バッファサイズ`W`のチャネル`c`を作成します。
    *   `M`個の`r`ゴルーチンを起動します。各ゴルーチンは`sys.Gosched()`によってスケジューリングヒントを与えられ、他のゴルーチンにCPUを譲る可能性があります。
    *   `s`ゴルーチンを起動し、メッセージの送信を開始します。

このテストは、複数の並行エンティティが共有リソース(この場合はチャネルと`h`配列)にアクセスする際の、Go言語の並行プリミティブの正確性と安全性を検証することを目的としています。特に、チャネルのバッファリングと`select`の動作が、競合状態を引き起こさずに正しく機能するかどうかを確認しています。

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

### `src/cmd/gc/go.y`

このファイルでは、`orange_stmt`(`range`ステートメントの文法規則)から以下の2つの規則が削除されています。

1.  `exprsym3 ':' exprsym3 '=' LRANGE expr`
    *   これは、`key:value = range expr`のような構文に対応していたと考えられます。`=`が含まれていることから、初期化を伴う`range`ループの形式だった可能性があります。
2.  `exprsym3 ':' exprsym3 LCOLAS LRANGE expr`
    *   これは、`key:value := range expr`のような構文に対応していたと考えられます。`LCOLAS`(`:=`)が含まれていることから、短い変数宣言を伴う`range`ループの形式だった可能性があります。

これらの削除により、コンパイラは`:`を用いた`range`ループのキーと値の宣言を認識しなくなります。

### `test/ken/chan1.go`

このファイルは新規追加されており、Go言語のチャネルとゴルーチンの動作を検証するための包括的な並行テストケースが含まれています。詳細は上記の「技術的詳細」セクションを参照してください。

### `test/ken/range.go`

このファイルでは、既存の`range`ループのテストケースが、新しい`key,value`構文に更新されています。

*   変更前:
    ```go
    for k:v := range a { ... }
    for k:v := range p { ... }
    for k:v := range m { ... }
    ```
*   変更後:
    ```go
    for k,v := range a { ... }
    for k,v := range p { ... }
    for k,v := range m { ... }
    ```
    コメントも`key:value`から`key,value`に修正されており、構文変更がテストコードにも反映されていることがわかります。

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

### `src/cmd/gc/go.y`の変更

この変更は、Go言語の構文解析器の核心部分に影響を与えます。`go.y`はGo言語の文法を定義しており、このファイルから生成されるパーサーがソースコードを解析します。古い`range`構文に対応する規則を削除することで、コンパイラはもはや`key:value`形式の`range`ループを有効なGoコードとして認識しなくなります。これは、言語の進化において、特定の構文が非推奨となり、最終的に削除されたことを示しています。これにより、Go言語の構文がより一貫性のあるものになり、開発者が覚えるべき構文規則が簡素化されました。

### `test/ken/chan1.go`の追加

この新しいテストファイルは、Go言語の並行処理モデルの正確性と堅牢性を保証するために非常に重要です。

*   **競合状態の検出**: `h`配列を使用して、メッセージの送信と受信の順序を厳密に追跡しています。`panicln`の呼び出しは、メッセージが期待される状態(例: 送信済みだが未受信)でない場合に、競合状態やチャネルの誤動作を示唆します。
*   **チャネルのバッファリングテスト**: `W = 2`というバッファサイズを持つチャネルを使用することで、バッファリングされたチャネルがどのように動作するかをテストしています。送信側はバッファが満杯になるまでブロックせず、受信側はバッファが空になるまでブロックしません。
*   **`select`の動作テスト**: 受信側ゴルーチン`r`は`select`を使用してチャネルからの受信を待機します。これは、複数のチャネル操作を扱うGoのイディオムであり、このテストは`select`が並行環境で正しく機能することを確認します。
*   **`sys.Gosched()`の利用**: `sys.Gosched()`は、現在のゴルーチンがCPUを他のゴルーチンに譲ることを示唆します。これは、テストにおいて意図的にゴルーチンのスケジューリングを変動させ、より多様な並行実行パスを探索し、潜在的な競合状態を露呈させるために使用されます。

このテストは、Go言語の並行処理プリミティブが、複雑な並行シナリオにおいても予測可能かつ安全に動作することを保証するための、初期段階での品質保証の取り組みを示しています。

### `test/ken/range.go`の変更

この変更は、`range`構文の変更が言語全体にわたって適用され、既存のテストコードも新しい構文に準拠するように更新されたことを示しています。テストコードが新しい構文に適合していることは、コンパイラの変更が正しく機能し、新しい構文が意図通りに動作することを検証する上で不可欠です。これにより、言語の進化が後方互換性を保ちつつ、新しい標準に移行していることが確認できます。

## 関連リンク

*   Go言語の`for`ステートメント(`range`を含む)に関する公式ドキュメント: [https://go.dev/ref/spec#For_statements](https://go.dev/ref/spec#For_statements)
*   Go言語のチャネルに関する公式ドキュメント: [https://go.dev/ref/spec#Channel_types](https://go.dev/ref/spec#Channel_types)
*   Go言語の初期の設計に関する議論(Go言語のメーリングリストやデザインドキュメントなど、当時の情報源を探す必要がありますが、一般公開されているものは少ないかもしれません。)

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

*   Go言語の公式ドキュメント (上記「関連リンク」に記載)
*   Yacc/Bisonの一般的な情報 (文法ファイルの理解のため)
*   Gitのdiff形式の解釈 (コミット内容の理解のため)
*   Go言語の初期のコミット履歴 (GitHubリポジトリ)
*   Go言語の設計哲学に関する一般的な知識 (シンプルさ、一貫性など)
*   並行処理の概念 (競合状態、デッドロックなど)

(注: 2009年当時のGo言語の設計に関する具体的な議論やドキュメントは、一般に公開されているものが限られているため、この解説はコミット内容と現在のGo言語の知識に基づいて推測される部分が多く含まれます。)
# [インデックス 1557] ファイルの概要

このコミットは、Go言語の初期開発段階における重要な構文変更と、並行処理のテストケース追加を含んでいます。具体的には、`range`キーワードを用いたループ構文において、キーと値の区切り文字が`:`から`,`へと変更されました。これに伴い、コンパイラの文法定義ファイルが更新され、古い構文のサポートが削除されています。また、Go言語の重要な機能であるチャネルの動作を検証するための新しい並行テストが追加されました。

## コミット

commit 7859ae8a2f5f5b48d5961df7c6e84ce7d7c3c46b Author: Ken Thompson ken@golang.org Date: Mon Jan 26 11:34:38 2009 -0800

removed a:b in range syntax
added another channel test

R=r
OCL=23488
CL=23488

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

[https://github.com/golang/go/commit/7859ae8a2f5f5b48d5961df7c6e84ce7d7c3c46b](https://github.com/golang/go/commit/7859ae8a2f5f5b48d5961df7c6e84ce7d7c3c46b)

## 元コミット内容

commit 7859ae8a2f5f5b48d5961df7c6e84ce7d7c3c46b Author: Ken Thompson ken@golang.org Date: Mon Jan 26 11:34:38 2009 -0800

removed a:b in range syntax
added another channel test

R=r
OCL=23488
CL=23488

src/cmd/gc/go.y | 12 ------------ test/ken/chan1.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/ken/range.go | 8 ++++---- 3 files changed, 60 insertions(+), 16 deletions(-)

diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y index e8843a97e6..0a7cd0813b 100644 --- a/src/cmd/gc/go.y +++ b/src/cmd/gc/go.y @@ -538,23 +538,11 @@ orange_stmt: $$ = nod(ORANGE, $1, $4); $$->etype = 0; // := flag } -|\texprsym3 ':' exprsym3 '=' LRANGE expr

  • {\n-\t\t$$ = nod(OLIST, $1, $3);\n-\t\t$$ = nod(ORANGE, $$, $6);\n-\t\t$$->etype = 0;\n-\t}\n |\texprsym3_list_r LCOLAS LRANGE expr {\n \t\t$$ = nod(ORANGE, $1, $4);\n \t\t$$->etype = 1;\n \t}\n-|\texprsym3 ':' exprsym3 LCOLAS LRANGE expr
  • {\n-\t\t$$ = nod(OLIST, $1, $3);\n-\t\t$$ = nod(ORANGE, $$, $6);\n-\t\t$$->etype = 1;\n-\t}\n for_header: osimple_stmt ';' orange_stmt ';' osimple_stmt diff --git a/test/ken/chan1.go b/test/ken/chan1.go new file mode 100644 index 0000000000..c6d7825b77 --- /dev/null +++ b/test/ken/chan1.go @@ -0,0 +1,56 @@ +// $G $D/$F.go && $L $F.$A && ./$A.out

+// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +const N = 1000; // sent messages +const M = 10; // receiving goroutines +const W = 2; // channel buffering +var h [N]int; // marking of send/recv + +func +r(c chan int, m int) +{

  • for {
  •      	select {
    
  •   case r := <- c:
    
  •   	if h[r] != 1 {
    
  •   		panicln("r",
    
  •   			"m=", m,
    
  •   			"r=", r,
    
  •   			"h=", h[r]
    
  •   		);
    
  •   	}
    
  •   	h[r] = 2;
    
  •   }
    
  •    }
    

+} + +func +s(c chan int) +{

  • for n:=0; n<N; n++ {
  •   r := n;
    
  •   if h[r] != 0 {
    
  •   	panicln("s");
    
  •   }
    
  •   h[r] = 1;
    
  •   c <- r;
    
  • } +}

+func +main() +{

  • c := make(chan int, W);

  • for m:=0; m<M; m++ {

  •   go r(c, m);
    
  •   sys.Gosched();
    
  • }

  • sys.Gosched();

  • sys.Gosched();

  • s(c); +} diff --git a/test/ken/range.go b/test/ken/range.go index 2417580ddb..55e168920b 100644 --- /dev/null +++ b/test/ken/range.go @@ -76,10 +76,10 @@ main() }

    /* -\t * key:value +\t * key,value */ i = 0; -\tfor k:v := range a {\n+\tfor k,v := range a {\n \t\tif v != f(k) {\n \t\t\tpanicln("key:value array range", k, v, a[k]);\n \t\t}\n@@ -90,7 +90,7 @@ main() }

    i = 0; -\tfor k:v := range p {\n+\tfor k,v := range p {\n \t\tif v != f(k) {\n \t\t\tpanicln("key:value pointer range", k, v, p[k]);\n \t\t}\n@@ -101,7 +101,7 @@ main() }

    i = 0; -\tfor k:v := range m {\n+\tfor k,v := range m {\n \t\tif v != f(k) {\n \t\t\tpanicln("key:value map range", k, v, m[k]);\n \t\t}\n```

変更の背景

このコミットは、Go言語の初期設計段階における言語構文の洗練と、並行処理モデルの堅牢性確保という二つの主要な背景を持っています。

  1. range構文の統一と簡素化: Go言語のrangeキーワードは、配列、スライス、文字列、マップ、チャネルといったコレクションの要素を反復処理するための強力な機能です。初期の設計では、マップや配列のインデックスと値を同時に取得する際に、for k:v := range collectionのようなkey:value形式の構文が検討されていた可能性があります。しかし、Go言語の設計哲学は「シンプルさ」と「明瞭さ」を重視しており、複数の値を宣言する際の一般的なパターン(例: x, y := func())との一貫性を保つことが重要でした。このコミットは、rangeループにおけるキーと値のペアの宣言を、他の多値代入と同様にカンマ区切り(key, value)に統一することで、言語全体の構文の一貫性を高め、学習コストを削減することを目的としています。

  2. チャネルテストの拡充: Go言語の並行処理は、ゴルーチンとチャネルを基盤としています。チャネルはゴルーチン間の安全な通信を可能にする重要なプリミティブであり、その正しい動作は言語の信頼性にとって不可欠です。初期の言語開発段階では、チャネルの様々な使用パターンやエッジケースを網羅的にテストすることが求められていました。このコミットで追加されたchan1.goは、複数のゴルーチンがチャネルを介してデータを送受信するシナリオをシミュレートし、チャネルのバッファリング、selectステートメント、および並行アクセスにおけるデータの一貫性を検証するためのものです。これにより、チャネルの実装が意図通りに機能し、競合状態やデッドロックなどの問題が発生しないことを保証しようとしています。

前提知識の解説

Go言語のrangeキーワード

Go言語のforループは、C言語のような伝統的なループ構文に加えて、rangeキーワードを用いたイテレーションをサポートしています。rangeは、配列、スライス、文字列、マップ、チャネルといったデータ構造を反復処理するために使用されます。

  • 配列とスライス: for index, value := range arrayOrSlice の形式で、インデックスと要素の値を取得できます。インデックスのみが必要な場合は for index := range arrayOrSlice、値のみが必要な場合は for _, value := range arrayOrSlice と書きます。
  • 文字列: for index, runeValue := range string の形式で、UTF-8エンコードされた文字列のバイトインデックスとUnicodeコードポイント(rune)を取得できます。
  • マップ: for key, value := range map の形式で、キーと値のペアを取得できます。マップのイテレーション順序は保証されません。
  • チャネル: for value := range channel の形式で、チャネルから値が送信されるたびにそれを受け取ります。チャネルが閉じられるとループは終了します。

このコミット以前は、マップや配列のキーと値の取得にkey:valueという構文が使われていた時期があったことが示唆されています。

Go言語のチャネルとゴルーチン

  • ゴルーチン (Goroutines): Go言語における軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数千、数万のゴルーチンを同時に実行することが可能です。goキーワードを関数の呼び出しの前に置くことで、その関数を新しいゴルーチンとして実行できます。
  • チャネル (Channels): ゴルーチン間で値を安全に送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送信できます。チャネルは、バッファリングされていない(同期)チャネルと、バッファリングされた(非同期)チャネルがあります。
    • バッファリングされていないチャネル: 送信操作は受信操作が完了するまでブロックし、受信操作は送信操作が完了するまでブロックします。これにより、ゴルーチン間の同期が保証されます。
    • バッファリングされたチャネル: 指定された数の値をバッファに保持できます。バッファが満杯になるまで送信操作はブロックせず、バッファが空になるまで受信操作はブロックしません。
  • selectステートメント: 複数のチャネル操作を同時に待機し、準備ができた最初の操作を実行するために使用されます。これにより、複雑な並行処理のパターンを簡潔に記述できます。

Yacc/Bison (go.y)

src/cmd/gc/go.yファイルは、Goコンパイラの字句解析器と構文解析器を生成するために使用されるYacc(Yet Another Compiler Compiler)またはBisonの文法定義ファイルです。Yacc/Bisonは、BNF(Backus-Naur Form)に似た形式で文法規則を記述することで、プログラミング言語の構文を定義し、それに対応するパーサーコードを自動生成するツールです。

  • .yファイルには、言語のトークン(終端記号)と非終端記号、そしてそれらの組み合わせによって構成される文法規則が記述されます。
  • 各規則には、その規則が認識されたときに実行されるアクション(通常はC言語のコード)を関連付けることができます。このアクションは、抽象構文木(AST)の構築やセマンティックチェックなどを行います。
  • このコミットでは、go.yからrange構文の古い形式(exprsym3 ':' exprsym3)に対応する文法規則が削除されています。これは、コンパイラが新しい構文のみを認識するように変更されたことを意味します。

技術的詳細

range構文の変更

このコミットの主要な変更点の一つは、Go言語のrangeループにおけるキーと値のペアの宣言構文がkey:valueからkey,valueに変更されたことです。

  • 変更前(推測される古い構文):

    for k:v := range collection {
        // ...
    }
    

    この構文は、Pythonの辞書イテレーションや他の言語のキーと値のペアを連想させるかもしれませんが、Go言語の多値代入(例: a, b := someFunc())の一般的なパターンとは異なっていました。

  • 変更後(現在のGo言語の構文):

    for k,v := range collection {
        // ...
    }
    

    この変更により、rangeループでの多値代入が、Go言語の他の場所での多値代入と一貫性を持つようになりました。これは、言語の設計原則である「シンプルさ」と「一貫性」を反映したものです。

src/cmd/gc/go.yの変更は、この構文変更をコンパイラレベルで反映しています。具体的には、orange_stmtrangeステートメントに関連する文法規則)から、:を含む古い形式の規則が削除されました。これにより、コンパイラは古い構文をエラーとして扱うようになります。

新しいチャネルテスト (test/ken/chan1.go)

test/ken/chan1.goは、Go言語のチャネルとゴルーチンの堅牢性を検証するために設計された新しいテストケースです。このテストは、以下の要素を組み合わせています。

  • 定数:
    • N = 1000: 送信するメッセージの総数。
    • M = 10: 受信側のゴルーチンの数。
    • W = 2: チャネルのバッファサイズ。
  • h配列: h [N]intは、各メッセージの送信/受信状態を追跡するための配列です。
    • 0: 未送信
    • 1: 送信済み(sゴルーチンによって設定)
    • 2: 受信済み(rゴルーチンによって設定)
  • r関数 (受信側ゴルーチン):
    • selectステートメントを使用して、チャネルcからの受信操作を待機します。
    • rを受信すると、h[r]1であることを確認します。もし1でなければ、paniclnを呼び出してエラーを報告します。これは、メッセージが送信される前に受信されたり、複数回受信されたりするなどの異常な状態を検出するためです。
    • h[r]2に設定し、メッセージが正常に受信されたことをマークします。
  • s関数 (送信側ゴルーチン):
    • 0からN-1までの整数をチャネルcに送信します。
    • 値を送信する前に、h[r]0であることを確認します。もし0でなければ、paniclnを呼び出してエラーを報告します。これは、メッセージが既に送信済みであるなどの異常な状態を検出するためです。
    • h[r]1に設定し、メッセージが送信されたことをマークします。
    • c <- rでチャネルに値を送信します。チャネルがバッファリングされているため、バッファが満杯でない限りブロックしません。
  • main関数:
    • バッファサイズWのチャネルcを作成します。
    • M個のrゴルーチンを起動します。各ゴルーチンはsys.Gosched()によってスケジューリングヒントを与えられ、他のゴルーチンにCPUを譲る可能性があります。
    • sゴルーチンを起動し、メッセージの送信を開始します。

このテストは、複数の並行エンティティが共有リソース(この場合はチャネルとh配列)にアクセスする際の、Go言語の並行プリミティブの正確性と安全性を検証することを目的としています。特に、チャネルのバッファリングとselectの動作が、競合状態を引き起こさずに正しく機能するかどうかを確認しています。

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

src/cmd/gc/go.y

このファイルでは、orange_stmtrangeステートメントの文法規則)から以下の2つの規則が削除されています。

  1. exprsym3 ':' exprsym3 '=' LRANGE expr
    • これは、key:value = range exprのような構文に対応していたと考えられます。=が含まれていることから、初期化を伴うrangeループの形式だった可能性があります。
  2. exprsym3 ':' exprsym3 LCOLAS LRANGE expr
    • これは、key:value := range exprのような構文に対応していたと考えられます。LCOLAS:=)が含まれていることから、短い変数宣言を伴うrangeループの形式だった可能性があります。

これらの削除により、コンパイラは:を用いたrangeループのキーと値の宣言を認識しなくなります。

test/ken/chan1.go

このファイルは新規追加されており、Go言語のチャネルとゴルーチンの動作を検証するための包括的な並行テストケースが含まれています。詳細は上記の「技術的詳細」セクションを参照してください。

test/ken/range.go

このファイルでは、既存のrangeループのテストケースが、新しいkey,value構文に更新されています。

  • 変更前:
    for k:v := range a { ... }
    for k:v := range p { ... }
    for k:v := range m { ... }
    
  • 変更後:
    for k,v := range a { ... }
    for k,v := range p { ... }
    for k,v := range m { ... }
    
    コメントもkey:valueからkey,valueに修正されており、構文変更がテストコードにも反映されていることがわかります。

コアとなるコードの解説

src/cmd/gc/go.yの変更

この変更は、Go言語の構文解析器の核心部分に影響を与えます。go.yはGo言語の文法を定義しており、このファイルから生成されるパーサーがソースコードを解析します。古いrange構文に対応する規則を削除することで、コンパイラはもはやkey:value形式のrangeループを有効なGoコードとして認識しなくなります。これは、言語の進化において、特定の構文が非推奨となり、最終的に削除されたことを示しています。これにより、Go言語の構文がより一貫性のあるものになり、開発者が覚えるべき構文規則が簡素化されました。

test/ken/chan1.goの追加

この新しいテストファイルは、Go言語の並行処理モデルの正確性と堅牢性を保証するために非常に重要です。

  • 競合状態の検出: h配列を使用して、メッセージの送信と受信の順序を厳密に追跡しています。paniclnの呼び出しは、メッセージが期待される状態(例: 送信済みだが未受信)でない場合に、競合状態やチャネルの誤動作を示唆します。
  • チャネルのバッファリングテスト: W = 2というバッファサイズを持つチャネルを使用することで、バッファリングされたチャネルがどのように動作するかをテストしています。送信側はバッファが満杯になるまでブロックせず、受信側はバッファが空になるまでブロックしません。
  • selectの動作テスト: 受信側ゴルーチンrselectを使用してチャネルからの受信を待機します。これは、複数のチャネル操作を扱うGoのイディオムであり、このテストはselectが並行環境で正しく機能することを確認します。
  • sys.Gosched()の利用: sys.Gosched()は、現在のゴルーチンがCPUを他のゴルーチンに譲ることを示唆します。これは、テストにおいて意図的にゴルーチンのスケジューリングを変動させ、より多様な並行実行パスを探索し、潜在的な競合状態を露呈させるために使用されます。

このテストは、Go言語の並行処理プリミティブが、複雑な並行シナリオにおいても予測可能かつ安全に動作することを保証するための、初期段階での品質保証の取り組みを示しています。

test/ken/range.goの変更

この変更は、range構文の変更が言語全体にわたって適用され、既存のテストコードも新しい構文に準拠するように更新されたことを示しています。テストコードが新しい構文に適合していることは、コンパイラの変更が正しく機能し、新しい構文が意図通りに動作することを検証する上で不可欠です。これにより、言語の進化が後方互換性を保ちつつ、新しい標準に移行していることが確認できます。

関連リンク

  • Go言語のforステートメント(rangeを含む)に関する公式ドキュメント: https://go.dev/ref/spec#For_statements
  • Go言語のチャネルに関する公式ドキュメント: https://go.dev/ref/spec#Channel_types
  • Go言語の初期の設計に関する議論(Go言語のメーリングリストやデザインドキュメントなど、当時の情報源を探す必要がありますが、一般公開されているものは少ないかもしれません。)

参考にした情報源リンク

  • Go言語の公式ドキュメント (上記「関連リンク」に記載)
  • Yacc/Bisonの一般的な情報 (文法ファイルの理解のため)
  • Gitのdiff形式の解釈 (コミット内容の理解のため)
  • Go言語の初期のコミット履歴 (GitHubリポジトリ)
  • Go言語の設計哲学に関する一般的な知識 (シンプルさ、一貫性など)
  • 並行処理の概念 (競合状態、デッドロックなど)

(注: 2009年当時のGo言語の設計に関する具体的な議論やドキュメントは、一般に公開されているものが限られているため、この解説はコミット内容と現在のGo言語の知識に基づいて推測される部分が多く含まれます。)