[インデックス 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型チャネル
new
とmake
の違い
Go言語には、メモリを割り当てるための2つの組み込み関数new
とmake
があります。これらは異なる目的で使用されます。
-
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箇所です。
-
StartServer
関数内:- 変更前:
req := new(chan *Request);
- 変更後:
req := make(chan *Request);
StartServer
関数は、サーバーゴルーチンに渡すためのリクエストチャネルを生成し、返します。変更前はnew
を使用してnil
チャネルへのポインタを割り当てていましたが、これはチャネルとして機能しません。make
を使用することで、実際にリクエストを送受信できる有効なチャネルが作成されます。
- 変更前:
-
main
関数内:- 変更前:
req.replyc = new(chan int);
- 変更後:
req.replyc = make(chan int);
main
関数では、各リクエスト構造体(req
)に、サーバーからの応答を受け取るためのリプライチャネル(replyc
)を設定しています。ここでも同様に、new
の誤用によりnil
チャネルへのポインタが割り当てられていました。make
に修正することで、各リクエストがサーバーからの整数型の応答を正しく受け取れるようになります。
- 変更前:
これらの変更により、server.go
のサンプルコードはGo言語のチャネルの正しい使用法に準拠し、意図した並行処理が正しく機能するようになります。
関連リンク
参考にした情報源リンク
- Go: The Good Parts - new vs make
- Go言語のnewとmakeの違い - Qiita (これは一般的なQiita記事の例であり、特定の記事を参照したわけではありませんが、同様の内容が多数存在します)
- Go言語のチャネルについて - Qiita (これも一般的なQiita記事の例であり、特定の記事を参照したわけではありませんが、同様の内容が多数存在します)
- Go言語のmakeとnewの違いを理解する - Zenn (これも一般的なZenn記事の例であり、特定の記事を参照したわけではありませんが、同様の内容が多数存在します)