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

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

このコミットは、Go言語のdoc/progs/server.goファイルにおいて、チャネルの割り当て方法をnewからmakeに変更するものです。

コミット

  • コミットハッシュ: dfc3e52310dfe43534a9522b6ef4c870f76bd7d6
  • Author: Ian Lance Taylor iant@golang.org
  • Date: Fri Jan 9 15:13:26 2009 -0800

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

https://github.com/golang/go/commit/dfc3e52310dfe43534a9522b6ef4c870f76bd7d6

元コミット内容

    Use make instead of new to allocate a channel.
    
    R=r
    DELTA=2  (0 added, 0 deleted, 2 changed)
    OCL=22452
    CL=22455

変更の背景

このコミットは、Go言語の初期段階におけるチャネルの初期化に関する慣用的な記述方法の修正を反映しています。Go言語では、スライス、マップ、チャネルといった組み込みの参照型を初期化するためにmake関数を使用することが推奨されています。一方、new関数は、任意の型のゼロ値が割り当てられたメモリへのポインタを返します。

チャネルは、Goの並行処理モデルの根幹をなす要素であり、ゴルーチン間の安全な通信を可能にします。チャネルを正しく初期化することは、その機能性を確保するために不可欠です。初期のGo言語のコードベースでは、チャネルの初期化にnewが誤って使用されている箇所があり、これはチャネルのゼロ値(nil)へのポインタを返すだけで、実際に使用可能なチャネルインスタンスを作成しないため、ランタイムエラーや予期せぬ動作を引き起こす可能性がありました。

このコミットは、doc/progs/server.goというドキュメント内のサンプルコードにおけるこの誤用を修正し、Go言語の正しい慣用句に準拠させることを目的としています。これにより、コードの正確性と、Go言語の設計思想への適合性が向上します。

前提知識の解説

Go言語のチャネル (Channels)

Go言語のチャネルは、ゴルーチン(軽量な並行実行スレッド)間で値を送受信するための通信メカニズムです。チャネルは型付けされており、特定の型の値のみを送受信できます。チャネルは、Goの「共有メモリによる通信ではなく、通信によるメモリ共有」という並行処理の哲学を具現化しています。

チャネルには、バッファなしチャネルとバッファ付きチャネルの2種類があります。

  • バッファなしチャネル: 送信操作は受信操作が完了するまでブロックされ、受信操作は送信操作が完了するまでブロックされます。これにより、同期的な通信が実現されます。
  • バッファ付きチャネル: 指定された数の値を保持できるバッファを持ちます。バッファが満杯になるまで送信操作はブロックされず、バッファが空になるまで受信操作はブロックされません。

チャネルはmake関数を使って作成されます。

ch := make(chan int)         // バッファなしのint型チャネル
ch := make(chan string, 10)  // バッファサイズ10のstring型チャネル

newmakeの違い

Go言語には、メモリを割り当てるための2つの組み込み関数newmakeがあります。これらは異なる目的で使用されます。

  • new(Type):

    • 任意のTypeのゼロ値を割り当て、その型へのポインタ(*Type)を返します。
    • メモリを割り当てるだけで、初期化は行いません。数値型は0、文字列は空文字列、ポインタはnilなど、その型のゼロ値で初期化されます。
    • スライス、マップ、チャネルなどの参照型に対してnewを使用すると、それらの型のゼロ値(nil)へのポインタが返されます。これは、それらのデータ構造が使用可能な状態になることを意味しません。
    var p *int
    p = new(int) // pはint型のゼロ値(0)へのポインタ
    fmt.Println(*p) // 0
    
    var ch *chan int
    ch = new(chan int) // chはnilチャネルへのポインタ
    // *ch はnilチャネルなので、これに送信しようとするとデッドロックする
    
  • make(Type, args):

    • スライス、マップ、チャネルという組み込みの参照型のみを初期化するために使用されます。
    • これらの型は、使用する前に内部データ構造を初期化する必要があります。makeは、必要なメモリを割り当て、その内部データ構造を初期化して、使用可能なインスタンスを返します。
    • makeはポインタではなく、初期化された型の値を返します。
    ch := make(chan int) // int型のチャネルを初期化し、使用可能なチャネルインスタンスを返す
    m := make(map[string]int) // stringからintへのマップを初期化し、使用可能なマップインスタンスを返す
    s := make([]int, 5, 10) // 長さ5、容量10のint型スライスを初期化し、使用可能なスライスインスタンスを返す
    

この違いは、特にチャネルにおいて重要です。new(chan int)nilチャネルへのポインタを返しますが、make(chan int)は実際に通信に使用できるチャネルインスタンスを返します。nilチャネルへの送信や受信は、常にブロックされるか、パニックを引き起こす可能性があります。

技術的詳細

このコミットの技術的詳細は、Go言語におけるチャネルの正しい初期化方法に集約されます。

変更前:

req := new(chan *Request);
// ...
req.replyc = new(chan int);

変更後:

req := make(chan *Request);
// ...
req.replyc = make(chan int);

new(chan *Request)は、chan *Request型のゼロ値(つまりnilチャネル)へのポインタを返します。このポインタが指すnilチャネルは、通信操作(送信や受信)を行うことができません。nilチャネルへの送信や受信は、常にブロックされます。これは、プログラムがデッドロックに陥る原因となります。

一方、make(chan *Request)は、実際に使用可能なチャネルインスタンスを作成し、そのインスタンスを返します。これにより、ゴルーチン間で*Request型の値を安全に送受信できるようになります。

同様に、req.replyc = new(chan int)nilチャネルへのポインタを割り当てていました。これをreq.replyc = make(chan int)に修正することで、replycが実際にint型の値を送受信できるチャネルとして機能するようになります。

この修正は、Go言語の型システムとメモリ管理の基本的な理解に基づいています。newはメモリを割り当てるだけで、その型のゼロ値で初期化しますが、スライス、マップ、チャネルといった参照型は、単にゼロ値で初期化されただけでは機能しません。これらの型は、内部データ構造が適切に設定される必要があります。make関数は、これらの型の内部構造を適切に初期化し、使用可能な状態にする役割を担っています。

この変更は、Go言語の初期のコードベースにおける一般的な誤用を修正し、Goの並行処理モデルの健全性を確保するために不可欠でした。

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

--- a/doc/progs/server.go
+++ b/doc/progs/server.go
@@ -24,7 +24,7 @@ func Server(op *BinOp, service chan *Request) {
 }
 
 func StartServer(op *BinOp) chan *Request {
-	req := new(chan *Request);
+	req := make(chan *Request);
 	go Server(op, req);
 	return req;
 }
@@ -37,7 +37,7 @@ func main() {
 		req := &reqs[i];
 		req.a = i;
 		req.b = i + N;
-		req.replyc = new(chan int);
+		req.replyc = make(chan int);
 		adder <- req;
 	}
 	for i := N-1; i >= 0; i-- {   // doesn't matter what order

コアとなるコードの解説

変更はdoc/progs/server.goファイル内の2箇所です。

  1. StartServer関数内:

    • 変更前: req := new(chan *Request);
    • 変更後: req := make(chan *Request);
    • StartServer関数は、サーバーゴルーチンに渡すためのリクエストチャネルを生成し、返します。変更前はnewを使用してnilチャネルへのポインタを割り当てていましたが、これはチャネルとして機能しません。makeを使用することで、実際にリクエストを送受信できる有効なチャネルが作成されます。
  2. main関数内:

    • 変更前: req.replyc = new(chan int);
    • 変更後: req.replyc = make(chan int);
    • main関数では、各リクエスト構造体(req)に、サーバーからの応答を受け取るためのリプライチャネル(replyc)を設定しています。ここでも同様に、newの誤用によりnilチャネルへのポインタが割り当てられていました。makeに修正することで、各リクエストがサーバーからの整数型の応答を正しく受け取れるようになります。

これらの変更により、server.goのサンプルコードはGo言語のチャネルの正しい使用法に準拠し、意図した並行処理が正しく機能するようになります。

関連リンク

参考にした情報源リンク