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

[インデックス 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が可能になったときに再開させる役割を担っています。

削除されたcrcwチャネルは、netFD(ネットワークファイルディスクリプタ)がpollServerに対して読み書きの待機を要求するための通信手段として設計されていたようです。しかし、このコミットが行われた2012年という時期は、Go言語がまだ比較的新しく、その内部実装が活発に開発・改善されていた時期にあたります。この変更は、よりシンプルで効率的なポーリングメカニズムが導入された結果、これらのチャネルが冗長になったか、あるいはより直接的な方法でポーリングサーバーとの連携が行われるようになったことを示唆しています。これにより、コードの複雑性が減少し、保守性が向上するとともに、潜在的なパフォーマンスの改善にも繋がる可能性があります。

前提知識の解説

本コミットを理解するためには、以下の概念を把握しておく必要があります。

  1. ノンブロッキングI/OとEAGAIN:

    • 通常のI/O操作(readwriteなど)は、データが利用可能になるか、書き込みバッファが空くまでブロックすることがあります。
    • ノンブロッキングI/Oは、I/O操作がすぐに完了しない場合、エラーコード(Unix系システムではEAGAINまたはEWOULDBLOCK)を返して即座に制御を呼び出し元に戻します。これにより、アプリケーションはI/Oが完了するのを待つ間に他の処理を行うことができます。
    • しかし、ノンブロッキングI/Oだけでは、いつI/Oが再開可能になるかを知る術がないため、効率的な待機メカニズムが必要です。
  2. I/O多重化(Polling):

    • 複数のファイルディスクリプタ(ソケットなど)の状態を監視し、いずれかのディスクリプタでI/Oが可能になったときに通知を受け取るメカニズムです。
    • これにより、単一のスレッドで多数のI/O操作を効率的に処理できます。
    • 主要なI/O多重化APIには、Linuxのepoll、macOS/BSDのkqueue、WindowsのIOCPなどがあります。これらは、多数のファイルディスクリプタを効率的に監視し、イベント駆動型でI/Oの準備ができたことをアプリケーションに通知します。
  3. Go言語のnetパッケージ:

    • Go言語でネットワーク通信を行うための主要なパッケージです。TCP/UDPソケット、HTTPクライアント/サーバーなどが含まれます。
    • 内部的には、OSのノンブロッキングI/OとI/O多重化機構を透過的に利用し、Goのゴルーチンとチャネルの並行処理モデルと統合されています。これにより、開発者はブロッキングI/Oのようにコードを記述しながら、実際にはノンブロッキングで効率的なネットワーク処理を実現できます。
  4. netFDpollServer:

    • netFD(Network File Descriptor): netパッケージ内でネットワーク接続を表す内部的な構造体で、OSのファイルディスクリプタをラップしています。
    • pollServer: netパッケージの内部で、ノンブロッキングI/O操作がEAGAINを返した際に、netFDがI/O可能になるまで待機するのを助ける役割を担う構造体です。これは、OSのI/O多重化機構(pollsterフィールドが指す低レベルなOSフック)と連携して動作します。
  5. 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以上)である必要がありました。

これらのチャネルと複雑なパイプによる起動メカニズムが削除されたということは、pollServernetFD間の通信方法が根本的に変更されたことを意味します。考えられる変更点としては、以下のようなものが挙げられます。

  1. runtime.pollパッケージとの統合: Goのランタイムには、runtime.pollという低レベルなI/Oポーリング機構が存在します。netパッケージは、このランタイムポーラーを内部的に利用しています。このコミットは、netパッケージが独自のチャネルベースの通信メカニズムを廃止し、より直接的にruntime.pollの機能を利用するようになったことを示唆している可能性があります。runtime.pollは、ゴルーチンを効率的にブロック・アンブロックするためのより最適化された内部メカニズムを持っているため、独自のチャネルを介した通信は不要になったと考えられます。
  2. pollsterの直接利用: pollServer構造体にはpoll *pollsterフィールドが残っています。pollsterはOS固有の低レベルなポーリングフックを抽象化したものです。チャネルを介した通信が不要になったことで、WaitRead()WaitWrite()メソッドが、pollsterを介して直接OSのポーリング機構にFDを登録し、I/O準備完了イベントを待機するゴルーチンを管理するようになった可能性があります。
  3. 簡素化と効率化: 複雑なチャネルとパイプによる起動メカニズムは、オーバーヘッドや潜在的なデッドロックのリスクを伴う可能性があります。これらのフィールドとロジックを削除することで、コードベースが簡素化され、I/Oポーリングの効率が向上したと考えられます。

残されたコメント「call s.WaitRead() or s.WaitWrite() to pass the request to the poll server.」は、WaitRead()WaitWrite()メソッド自体は引き続きpollServerとのインタフェースとして機能するものの、その内部実装がチャネルベースからより直接的で効率的なメカニズムに移行したことを示しています。

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

本コミットにおけるコアとなるコードの変更箇所は以下の2ファイルです。

  1. src/pkg/net/fd.go:

    • pollServer構造体から、crcwという2つのchan *netFD型のフィールドが削除されました。
      -	cr, cw     chan *netFD // buffered >= 1
      
    • pollServerの役割を説明するコメントブロックから、s.crおよびs.cwチャネルを介したリクエスト送信、パイプによるポーリングサーバーの起動、および競合状態の回避に関する詳細な説明が削除されました。残されたコメントは、s.WaitRead()またはs.WaitWrite()を呼び出すことでリクエストをポーリングサーバーに渡すという、より抽象化された説明になっています。
  2. src/pkg/net/newpollserver.go:

    • newPollServer関数内で、pollServer構造体のインスタンスscrおよび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自身を送信していました。これらのチャネルは、netFDpollServer間の明示的な通信パスとして機能していました。これらが削除されたということは、pollServernetFDからの待機リクエストを、チャネルを介さずに直接、あるいは別のより低レベルなメカニズム(例えば、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に関する一般的な知識