Go netpoller
Goランタイムが持つ、I/Oの多重化(epoll/kqueue/IOCPなど)をgoroutineスケジューラと統合する内部レイヤー。GoのosReadがO_NONBLOCK+EAGAINでもブロックする問題の直接の原因になっている仕組み。
設計思想
Goの標準ライブラリは、ソケットやファイルへのRead/Writeをアプリケーションコードから見て素朴な「ブロッキング呼び出し」のように書かせる。コールバックベースの非同期I/O(イベントループにハンドラを登録するスタイル)は制御フローを追いにくくするため、Goはその複雑さをランタイム内部に押し込め、開発者にはepoll_create/epoll_ctl/epoll_waitを一切触らせない設計を選んでいる。
主要な関数(Linux実装: runtime/netpoll_epoll.go)
netpollinit()— epollインスタンスの作成。プログラム全体で1回だけ呼ばれる。netpollopen(fd, pd)— 対象のファイルディスクリプタをエッジトリガーでepoll_ctlに登録する。netpoll(delta)—epoll_waitを実行する本体。deltaが負なら無限待機、0ならノンブロック、正ならその時間だけ待機。netpollBreak()—netpollがブロック中のときに強制的に起こすための唤起機構。
pollDesc とgoroutineの休止・再開
各fdにはpollDescという構造体が対応付けられ、その fd を読み待ち/書き待ちしているgoroutineへのポインタ(rg/wg)を保持する。
- 休止時:
read(2)などがEAGAINを返すと、ランタイムはnetpollblockを呼び、goparkで該当goroutineをスケジューラから外して休眠させる。この時点でOSスレッド(M)は解放され、他のgoroutineの実行に回せる。 - 再開時:
netpollがepoll_waitでfdのready状態を検出すると、対応するpollDescから休止中のgoroutineをgoreadyで実行可能状態に戻す。
スケジューラ(GMPモデル)との統合
goroutineスケジューラのfindRunnableは、実行可能なgoroutineを探す過程でruntime.netpollを呼び出す。さらに、バックグラウンドで動くsysmonスレッドも約10ms間隔でnetpollを実行し、非同期的にI/Oイベントを拾い上げる。この統合により、「netpollが1個で済んでいるランタイム全体の窓口」として機能し、複数スレッドが同時にepollへブロックしないよう排他制御されている。
出典
- go/src/runtime/netpoll.go
- go/src/runtime/netpoll_epoll.go
- Explaining the Golang I/O multiplexing netpoller model - SoByte