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

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

このコミットは、Go言語の仕様書 doc/go_spec.html を更新し、クローズされたチャネルへの送信がどのように振る舞うかを明確に定義しています。具体的には、クローズされたチャネルへの送信が「進行する (proceeding)」と見なされ、その結果としてランタイムパニックを引き起こすことを明記しています。これにより、select ステートメントの動作記述を簡素化し、仕様の曖昧さを解消しています。

コミット

commit e7a138b856b21bc3db8ddb1dfe9f3fabe0dc59e8
Author: Russ Cox <rsc@golang.org>
Date:   Wed Feb 8 15:24:48 2012 -0500

    spec: send on closed channel counts as "proceeding"
    
    Other wordings are possible but defining this as one
    of the ways to proceed means we don't have to add
    language about this to the description of select.
    
    Fixes #2825.
    
    R=golang-dev, gri, r
    CC=golang-dev
    https://golang.org/cl/5643062

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

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

元コミット内容

spec: send on closed channel counts as "proceeding"

Other wordings are possible but defining this as one
of the ways to proceed means we don't have to add
language about this to the description of select.

Fixes #2825.

R=golang-dev, gri, r
CC=golang-dev
https://golang.org/cl/5643062

変更の背景

Go言語のチャネルは、ゴルーチン間の安全な通信を可能にする強力な並行処理プリミティブです。しかし、チャネルがクローズされた後にそのチャネルに値を送信しようとした場合の正確な振る舞いについて、初期のGo言語仕様では明確な記述が不足していました。

この曖昧さは、特に select ステートメント内でクローズされたチャネルへの送信操作がどのように扱われるかという点で問題を引き起こす可能性がありました。select ステートメントは、複数の通信操作の中から準備ができたものを選択するメカニズムですが、チャネルがクローズされた場合の送信操作が「準備ができた」と見なされるのか、それともブロックするのか、あるいは別の結果になるのかが不明確でした。

このコミットの目的は、この曖昧さを解消し、クローズされたチャネルへの送信がランタイムパニックを引き起こすという既存の実装の振る舞いを仕様として明文化することです。これにより、select ステートメントの記述を簡素化し、言語のセマンティクスをより明確にすることが可能になりました。コミットメッセージにある「Other wordings are possible but defining this as one of the ways to proceed means we don't have to add language about this to the description of select.」という記述は、この変更が select の複雑な記述を避けるための設計上の選択であることを示唆しています。

前提知識の解説

Go言語のチャネル (Channels)

Go言語のチャネルは、ゴルーチン間で値を送受信するための通信メカニズムです。チャネルは make(chan Type) で作成され、Type はチャネルが送受信する要素の型です。

  • 送信 (Send): ch <- value の形式でチャネルに値を送信します。
  • 受信 (Receive): value := <-ch または <-ch の形式でチャネルから値を受信します。

チャネルには以下の2種類があります。

  1. バッファなしチャネル (Unbuffered Channels): make(chan Type) で作成されます。送信操作は受信操作が準備できるまでブロックし、受信操作は送信操作が準備できるまでブロックします。つまり、送信と受信が同時に行われる必要があります。
  2. バッファありチャネル (Buffered Channels): make(chan Type, capacity) で作成されます。capacity はチャネルが保持できる要素の最大数です。バッファが満杯でない限り、送信操作はブロックしません。バッファが空でない限り、受信操作はブロックしません。

チャネルのクローズ (Closing Channels)

チャネルは close(ch) 関数を使ってクローズすることができます。チャネルをクローズすると、それ以上そのチャネルに値を送信することはできません。しかし、クローズされたチャネルからは、バッファに残っている値があればそれらを受信できます。バッファが空になった後、クローズされたチャネルから受信しようとすると、その型のゼロ値と、チャネルがクローズされたことを示す false のブール値が返されます(例: value, ok := <-ch)。

ランタイムパニック (Run-time Panics)

Go言語におけるパニックは、プログラムの通常の実行フローを中断させるエラーの一種です。パニックは、通常、回復不可能なエラー(例: nilポインタのデリファレンス、配列の範囲外アクセス)が発生した場合に発生します。パニックが発生すると、現在のゴルーチンの実行が停止し、遅延関数 (deferred functions) が実行された後、プログラム全体がクラッシュするか、recover 関数によってパニックが捕捉されない限り、スタックトレースが出力されます。

select ステートメント

select ステートメントは、複数のチャネル操作を待機し、そのうちのいずれかが準備できたときに実行されるGo言語の制御構造です。selectswitch ステートメントに似ていますが、case がチャネルの送受信操作である点が異なります。

selectcase は、以下のいずれかの条件が満たされたときに「準備ができた (ready)」と見なされます。

  • 送信操作: チャネルが送信を受け入れる準備ができている(バッファに空きがある、または受信側が準備できている)。
  • 受信操作: チャネルから値を受信する準備ができている(チャネルに値がある、またはチャネルがクローズされており、バッファが空になっている)。

複数の case が同時に準備できた場合、select はランダムに1つを選択して実行します。どの case も準備できていない場合、default case があればそれが実行されます。default case がなく、どの case も準備できていない場合は、いずれかの case が準備できるまで select ステートメントはブロックします。

技術的詳細

このコミットが導入する最も重要な技術的変更は、Go言語の仕様において「クローズされたチャネルへの送信」の振る舞いを明確に定義した点です。以前は、この状況がどのように扱われるかについて、仕様書には明示的な記述がありませんでした。しかし、Go言語のランタイムは、実際にはクローズされたチャネルへの送信を常にランタイムパニックとして処理していました。このコミットは、この既存の振る舞いを正式に仕様に組み込むものです。

具体的には、チャネルへの送信操作が「進行する (proceeding)」条件の一つとして、「クローズされたチャネルへの送信がランタイムパニックを引き起こすことによって進行する」という記述が追加されました。

この変更の技術的な意味合いは以下の通りです。

  1. 明確なセマンティクス: クローズされたチャネルへの送信は、もはや未定義の振る舞いではなく、明確に定義されたエラー条件(パニック)となります。これにより、開発者はこの状況を予測し、適切にコードを設計できるようになります。
  2. select ステートメントの簡素化: select ステートメントは、その case が「進行できる」かどうかを評価します。このコミット以前は、クローズされたチャネルへの送信が selectcase 内でどのように扱われるかについて、特別な考慮が必要でした。しかし、この変更により、クローズされたチャネルへの送信も「進行する」操作(ただしパニックを伴う)として扱われるため、select のロジックを複雑にすることなく、このケースを自然に組み込むことができます。つまり、select は、クローズされたチャネルへの送信 case が「準備ができた」と判断し、それを実行しようとしますが、その結果としてパニックが発生します。これにより、select の内部実装や仕様記述において、クローズされたチャネルへの送信を特別扱いする必要がなくなります。
  3. 一貫性: この変更は、Go言語の他の部分におけるエラー処理(特に回復不可能なエラーに対するパニックの使用)との一貫性を保ちます。チャネルがクローズされた後に送信を試みることは、通常、プログラマの論理的な誤りを示すものであり、パニックはこのような誤りを早期に発見するのに役立ちます。

このコミットは、Go言語の安定性と予測可能性を高める上で重要な役割を果たしています。

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

変更は doc/go_spec.html ファイルのチャネル送信に関するセクションで行われています。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -3687,10 +3687,10 @@ Channel  = Expression .
 
 <p>
 Both the channel and the value expression are evaluated before communication
-begins. Communication blocks until the send can proceed, at which point the
-value is transmitted on the channel.
+begins. Communication blocks until the send can proceed.
 A send on an unbuffered channel can proceed if a receiver is ready.
 A send on a buffered channel can proceed if there is room in the buffer.
+A send on a closed channel proceeds by causing a <a href="#Run_time_panics">run-time panic</a>.
 A send on a <code>nil</code> channel blocks forever.
 </p>
 

コアとなるコードの解説

このコミットでは、doc/go_spec.html 内のチャネル送信に関する段落が修正されています。

変更前:

Both the channel and the value expression are evaluated before communication
begins. Communication blocks until the send can proceed, at which point the
value is transmitted on the channel.
A send on an unbuffered channel can proceed if a receiver is ready.
A send on a buffered channel can proceed if there is room in the buffer.
A send on a <code>nil</code> channel blocks forever.

この記述では、「送信が進行できるまで通信はブロックする」と述べられていますが、クローズされたチャネルへの送信が「進行できる」条件に含まれるかどうかが不明確でした。

変更後:

Both the channel and the value expression are evaluated before communication
begins. Communication blocks until the send can proceed.
A send on an unbuffered channel can proceed if a receiver is ready.
A send on a buffered channel can proceed if there is room in the buffer.
A send on a closed channel proceeds by causing a <a href="#Run_time_panics">run-time panic</a>.
A send on a <code>nil</code> channel blocks forever.

変更点とそれぞれの意味は以下の通りです。

  1. - value is transmitted on the channel. の削除:

    • これは、送信が進行した結果として値がチャネルに送信されるという、自明な部分を削除したものです。クローズされたチャネルへの送信は値が送信されないため、この文言は不適切になるため削除されました。
  2. + A send on a closed channel proceeds by causing a <a href="#Run_time_panics">run-time panic</a>. の追加:

    • これがこのコミットの核心です。クローズされたチャネルへの送信が「進行する」条件の一つとして明示的に追加されました。ただし、その進行はランタイムパニックを引き起こすという結果を伴います。
    • <a href="#Run_time_panics">run-time panic</a> のリンクは、Go言語仕様書内のランタイムパニックのセクションへの内部リンクであり、読者がパニックについてさらに詳しく学ぶことができるように配慮されています。

この変更により、Go言語のチャネル送信のセマンティクスがより厳密かつ明確になり、特にクローズされたチャネルへの送信というエッジケースの振る舞いが公式に定義されました。

関連リンク

注記: コミットメッセージに記載されている Fixes #2825 の元のGitHub Issue 2825は、現在のGoリポジトリのIssueトラッカーでは直接見つけることができませんでした。これは、GoプロジェクトのIssueトラッカーが時間とともに移行されたり、Issue番号が再利用されたりする可能性があるためです。また、https://golang.org/cl/5643062 のChange List (CL) リンクも、現在のGerritシステムでは直接アクセスできませんでした。これは、古いCL番号がアーカイブされたか、URL構造が変更されたためと考えられます。しかし、コミットメッセージと差分から、変更の意図と内容を十分に理解できます。

参考にした情報源リンク

  • Go言語の公式ドキュメント (Go Programming Language Specification)
  • Go言語の公式ブログ (The Go Blog)
  • Go言語のソースコードリポジトリ (GitHub: golang/go)
  • Go言語のチャネル、パニック、select ステートメントに関する一般的な知識