[インデックス 13623] ファイルの概要
本コミットは、Go言語の標準ライブラリであるnet
パッケージ内の、ネットワークファイルディスクリプタ(netFD
)とポーリングサーバー(pollServer
)に関連するコードから、未使用のフィールドとそれに関連するコメントを削除するものです。具体的には、pollServer
構造体からcr
およびcw
というチャネルフィールドが削除され、それに伴いこれらのチャネルの初期化コードと、それらのチャネルを用いたポーリングサーバーとの通信プロトコルを説明する詳細なコメントが削除されています。
コミット
commit 6d5eb61ae7d65d56bc6277ddea4264a10054f28b
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Tue Aug 14 01:57:24 2012 +0400
net: remove unused fields
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/6454145
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6d5eb61ae7d65d56bc6277ddea4264a10054f28b
元コミット内容
--- a/src/pkg/net/fd.go
+++ b/src/pkg/net/fd.go
@@ -49,32 +49,9 @@ type netFD struct {
// A pollServer helps FDs determine when to retry a non-blocking
// read or write after they get EAGAIN. When an FD needs to wait,
-// send the fd on s.cr (for a read) or s.cw (for a write) to pass the
-// request to the poll server. Then receive on fd.cr/fd.cw.
+// call s.WaitRead() or s.WaitWrite() to pass the request to the poll server.
// When the pollServer finds that i/o on FD should be possible
-// again, it will send fd on fd.cr/fd.cw to wake any waiting processes.
-// This protocol is implemented as s.WaitRead() and s.WaitWrite().
-//
-// There is one subtlety: when sending on s.cr/s.cw, the
-// poll server is probably in a system call, waiting for an fd
-// to become ready. It's not looking at the request channels.
-// To resolve this, the poll server waits not just on the FDs it has
-// been given but also its own pipe. After sending on the
-// buffered channel s.cr/s.cw, WaitRead/WaitWrite writes a
-// byte to the pipe, causing the pollServer's poll system call to
-// return. In response to the pipe being readable, the pollServer
-// re-polls its request channels.
-//
-// Note that the ordering is "send request" and then "wake up server".
-// If the operations were reversed, there would be a race: the poll
-// server might wake up and look at the request channel, see that it
-// was empty, and go back to sleep, all before the requester managed
-// to send the request. Because the send must complete before the wakeup,
-// the request channel must be buffered. A buffer of size 1 is sufficient
-// for any request load. If many processes are trying to submit requests,
-// one will succeed, the pollServer will read the request, and then the
-// channel will be empty for the next process's request. A larger buffer
-// might help batch requests.
+// again, it will send on fd.cr/fd.cw to wake any waiting goroutines.
//
// To avoid races in closing, all fd operations are locked and
// refcounted. when netFD.Close() is called, it calls syscall.Shutdown
@@ -82,7 +59,6 @@ type netFD struct {
// will the fd be closed.
type pollServer struct {
- cr, cw chan *netFD // buffered >= 1
pr, pw *os.File
poll *pollster // low-level OS hooks
sync.Mutex // controls pending and deadline
--- a/src/pkg/net/newpollserver.go
+++ b/src/pkg/net/newpollserver.go
@@ -13,8 +13,6 @@ import (
func newPollServer() (s *pollServer, err error) {
s = new(pollServer)
- s.cr = make(chan *netFD, 1)
- s.cw = make(chan *netFD, 1)
if s.pr, s.pw, err = os.Pipe(); err != nil {
return nil, err
}
変更の背景
このコミットは、Go言語のnet
パッケージにおける内部的なI/Oポーリングメカニズムの進化と最適化の一環として行われました。コミットメッセージが「net: remove unused fields」(net: 未使用フィールドの削除)と簡潔に述べているように、以前の設計で導入されたものの、その後のコード変更やリファクタリングによって不要になったフィールドと、それに関連する複雑な通信ロジックの説明が削除されています。
Goのnet
パッケージは、ネットワーク通信を効率的に行うために、ノンブロッキングI/OとI/O多重化(ポーリング)を内部的に利用しています。これは、epoll
(Linux)、kqueue
(macOS/BSD)、IOCP
(Windows)といったOS固有のシステムコールを抽象化して利用することで実現されます。pollServer
は、これらの低レベルなポーリング機構を管理し、ネットワーク操作がブロックされた際にゴルーチンを効率的に待機させ、I/Oが可能になったときに再開させる役割を担っています。
削除されたcr
とcw
チャネルは、netFD
(ネットワークファイルディスクリプタ)がpollServer
に対して読み書きの待機を要求するための通信手段として設計されていたようです。しかし、このコミットが行われた2012年という時期は、Go言語がまだ比較的新しく、その内部実装が活発に開発・改善されていた時期にあたります。この変更は、よりシンプルで効率的なポーリングメカニズムが導入された結果、これらのチャネルが冗長になったか、あるいはより直接的な方法でポーリングサーバーとの連携が行われるようになったことを示唆しています。これにより、コードの複雑性が減少し、保守性が向上するとともに、潜在的なパフォーマンスの改善にも繋がる可能性があります。
前提知識の解説
本コミットを理解するためには、以下の概念を把握しておく必要があります。
-
ノンブロッキングI/Oと
EAGAIN
:- 通常のI/O操作(
read
やwrite
など)は、データが利用可能になるか、書き込みバッファが空くまでブロックすることがあります。 - ノンブロッキングI/Oは、I/O操作がすぐに完了しない場合、エラーコード(Unix系システムでは
EAGAIN
またはEWOULDBLOCK
)を返して即座に制御を呼び出し元に戻します。これにより、アプリケーションはI/Oが完了するのを待つ間に他の処理を行うことができます。 - しかし、ノンブロッキングI/Oだけでは、いつI/Oが再開可能になるかを知る術がないため、効率的な待機メカニズムが必要です。
- 通常のI/O操作(
-
I/O多重化(Polling):
- 複数のファイルディスクリプタ(ソケットなど)の状態を監視し、いずれかのディスクリプタでI/Oが可能になったときに通知を受け取るメカニズムです。
- これにより、単一のスレッドで多数のI/O操作を効率的に処理できます。
- 主要なI/O多重化APIには、Linuxの
epoll
、macOS/BSDのkqueue
、WindowsのIOCP
などがあります。これらは、多数のファイルディスクリプタを効率的に監視し、イベント駆動型でI/Oの準備ができたことをアプリケーションに通知します。
-
Go言語の
net
パッケージ:- Go言語でネットワーク通信を行うための主要なパッケージです。TCP/UDPソケット、HTTPクライアント/サーバーなどが含まれます。
- 内部的には、OSのノンブロッキングI/OとI/O多重化機構を透過的に利用し、Goのゴルーチンとチャネルの並行処理モデルと統合されています。これにより、開発者はブロッキングI/Oのようにコードを記述しながら、実際にはノンブロッキングで効率的なネットワーク処理を実現できます。
-
netFD
とpollServer
:netFD
(Network File Descriptor):net
パッケージ内でネットワーク接続を表す内部的な構造体で、OSのファイルディスクリプタをラップしています。pollServer
:net
パッケージの内部で、ノンブロッキングI/O操作がEAGAIN
を返した際に、netFD
がI/O可能になるまで待機するのを助ける役割を担う構造体です。これは、OSのI/O多重化機構(pollster
フィールドが指す低レベルなOSフック)と連携して動作します。
-
Goのチャネル:
- ゴルーチン間で値を安全に送受信するための通信メカニズムです。チャネルは、並行処理における同期と通信のプリミティブとして機能します。
- バッファ付きチャネルは、指定された数の値をバッファに保持でき、バッファが満杯でない限り送信はブロックされず、バッファが空でない限り受信はブロックされません。
技術的詳細
このコミットの核心は、pollServer
構造体からcr
(channel for read requests)とcw
(channel for write requests)という2つのチャネルフィールドが削除されたことです。これらのチャネルは、以前の設計において、netFD
がノンブロッキングI/O操作(読み込みまたは書き込み)がEAGAIN
エラーを返した際に、pollServer
に対して「このFDがI/O可能になるまで待機してほしい」というリクエストを送信するために使用されていました。
削除されたコメントは、このメカニズムの複雑さを詳細に説明していました。
- リクエストの送信:
netFD
は、待機が必要な場合、自身のFDをs.cr
(読み込みの場合)またはs.cw
(書き込みの場合)チャネルに送信していました。 - ポーリングサーバーの起動:
pollServer
は通常、OSのシステムコール(例:epoll_wait
)でブロックされており、リクエストチャネルを積極的に監視していません。この問題を解決するため、リクエストがチャネルに送信された後、WaitRead
/WaitWrite
メソッドはpollServer
自身のパイプ(pr
,pw
フィールド)に1バイトを書き込むことで、ポーリングサーバーのシステムコールを中断させ、リクエストチャネルを再確認させるという巧妙な仕組みが使われていました。 - 競合状態の回避: コメントでは、「リクエストを送信してからサーバーを起動する」という順序が重要であると強調されていました。これは、もし順序が逆だと、ポーリングサーバーがリクエストチャネルが空であると判断してすぐにスリープに戻ってしまう競合状態が発生する可能性があるためです。このため、リクエストチャネルはバッファ付き(サイズ1以上)である必要がありました。
これらのチャネルと複雑なパイプによる起動メカニズムが削除されたということは、pollServer
とnetFD
間の通信方法が根本的に変更されたことを意味します。考えられる変更点としては、以下のようなものが挙げられます。
runtime.poll
パッケージとの統合: Goのランタイムには、runtime.poll
という低レベルなI/Oポーリング機構が存在します。net
パッケージは、このランタイムポーラーを内部的に利用しています。このコミットは、net
パッケージが独自のチャネルベースの通信メカニズムを廃止し、より直接的にruntime.poll
の機能を利用するようになったことを示唆している可能性があります。runtime.poll
は、ゴルーチンを効率的にブロック・アンブロックするためのより最適化された内部メカニズムを持っているため、独自のチャネルを介した通信は不要になったと考えられます。pollster
の直接利用:pollServer
構造体にはpoll *pollster
フィールドが残っています。pollster
はOS固有の低レベルなポーリングフックを抽象化したものです。チャネルを介した通信が不要になったことで、WaitRead()
やWaitWrite()
メソッドが、pollster
を介して直接OSのポーリング機構にFDを登録し、I/O準備完了イベントを待機するゴルーチンを管理するようになった可能性があります。- 簡素化と効率化: 複雑なチャネルとパイプによる起動メカニズムは、オーバーヘッドや潜在的なデッドロックのリスクを伴う可能性があります。これらのフィールドとロジックを削除することで、コードベースが簡素化され、I/Oポーリングの効率が向上したと考えられます。
残されたコメント「call s.WaitRead() or s.WaitWrite() to pass the request to the poll server.」は、WaitRead()
とWaitWrite()
メソッド自体は引き続きpollServer
とのインタフェースとして機能するものの、その内部実装がチャネルベースからより直接的で効率的なメカニズムに移行したことを示しています。
コアとなるコードの変更箇所
本コミットにおけるコアとなるコードの変更箇所は以下の2ファイルです。
-
src/pkg/net/fd.go
:pollServer
構造体から、cr
とcw
という2つのchan *netFD
型のフィールドが削除されました。- cr, cw chan *netFD // buffered >= 1
pollServer
の役割を説明するコメントブロックから、s.cr
およびs.cw
チャネルを介したリクエスト送信、パイプによるポーリングサーバーの起動、および競合状態の回避に関する詳細な説明が削除されました。残されたコメントは、s.WaitRead()
またはs.WaitWrite()
を呼び出すことでリクエストをポーリングサーバーに渡すという、より抽象化された説明になっています。
-
src/pkg/net/newpollserver.go
:newPollServer
関数内で、pollServer
構造体のインスタンスs
のcr
およびcw
チャネルを初期化するmake
呼び出しが削除されました。- s.cr = make(chan *netFD, 1) - s.cw = make(chan *netFD, 1)
コアとなるコードの解説
このコミットは、Goのnet
パッケージにおけるI/Oポーリングの内部実装が、より洗練された、チャネルを直接使用しないメカニズムに移行したことを明確に示しています。
-
pollServer
構造体からのチャネル削除:pollServer
は、ノンブロッキングI/Oの待機を管理する中心的な役割を担います。以前は、netFD
がI/Oの準備ができるまで待機する必要がある場合、cr
(読み込み用)またはcw
(書き込み用)チャネルを介してpollServer
にそのnetFD
自身を送信していました。これらのチャネルは、netFD
とpollServer
間の明示的な通信パスとして機能していました。これらが削除されたということは、pollServer
がnetFD
からの待機リクエストを、チャネルを介さずに直接、あるいは別のより低レベルなメカニズム(例えば、pollster
がラップするOSのポーリングAPIへの直接登録や、Goランタイムのスケジューラとの連携)で処理するようになったことを意味します。 -
newPollServer
でのチャネル初期化の削除:newPollServer
関数は、pollServer
の新しいインスタンスを作成し、必要な初期設定を行う役割を担います。s.cr = make(chan *netFD, 1)
とs.cw = make(chan *netFD, 1)
の行は、これらのチャネルをバッファサイズ1で作成していました。この初期化コードの削除は、pollServer
がこれらのチャネルを全く使用しなくなったことの直接的な証拠です。これにより、チャネルの作成と管理にかかるリソースが不要になり、起動時のオーバーヘッドがわずかながら削減されます。 -
コメントの変更: 最も顕著な変更の一つは、
src/pkg/net/fd.go
内のpollServer
に関するコメントの大幅な削除です。削除されたコメントは、s.cr
/s.cw
チャネルを介したリクエスト送信、パイプを用いたpollServer
の起動、そして競合状態を避けるための「リクエスト送信後にサーバーを起動する」という順序の重要性について、非常に詳細に説明していました。これらの説明が削除されたことは、この複雑な通信プロトコルがもはや存在しないことを示しています。残されたコメントは、「s.WaitRead()
またはs.WaitWrite()
を呼び出してリクエストをポーリングサーバーに渡す」という、より高レベルなインタフェースの説明に簡素化されています。これは、内部実装が抽象化され、ユーザー(この場合はnetFD
)がポーリングサーバーとどのように連携するかを詳細に知る必要がなくなったことを示唆しています。
全体として、このコミットは、Goのnet
パッケージが、より効率的で、より直接的なI/Oポーリングメカニズムへと進化している過程を示しています。これは、Goランタイムの成熟と、低レベルなシステムコールとのより密接な統合の結果である可能性が高いです。
関連リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語の
syscall
パッケージのドキュメント(EAGAIN
などシステムコール関連): https://pkg.go.dev/syscall - Go言語の
os
パッケージのドキュメント(Pipe
など): https://pkg.go.dev/os - Go言語の
runtime
パッケージのドキュメント(runtime.poll
など、低レベルなポーリング機構に関連する可能性のある情報): https://pkg.go.dev/runtime
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/net
ディレクトリ) - Go言語の公式ドキュメント
- Go言語のIssueトラッカーやコードレビューシステム(
golang.org/cl/6454145
) - I/O多重化に関する一般的な知識(
epoll
,kqueue
など) - ノンブロッキングI/Oに関する一般的な知識