bigywater.tech
Go · 58

网络轮询器:阻塞的网络 IO 为什么不占线程

Go 里“一个连接一个 goroutine”的写法能撑起几万个连接,关键在网络轮询器:阻塞的网络 IO 并不真的阻塞一个线程。这篇用线程数和 goroutine 状态验证:连接多了线程不增长,而绕开轮询器的阻塞系统调用则一个占一个线程。

5 → 12
2000 个阻塞在 Read 上的网络 goroutine 把线程数推高到的范围
+1 vs +198
200 个 goroutine 读 pipe:通过 os.File 走轮询器与直接 syscall.Read 的新增线程数
1 个
本进程持有的 kqueue 描述符数,与连接数无关

连接多了线程不多

实测 · 本机执行输出
  100 条 TCP 连接,服务端每条一个 goroutine 阻塞在 Read 上:NumGoroutine = 102;线程创建数 5 → 5
  goroutine 状态:[IO wait]×101 [running]×1 
  lsof 里属于本进程的 KQUEUE 描述符:4u KQUEUE
  2000 条 TCP 连接,服务端每条一个 goroutine 阻塞在 Read 上:NumGoroutine = 2002;线程创建数 5 → 12
  goroutine 状态:[IO wait]×2001 [running]×1 
  lsof 里属于本进程的 KQUEUE 描述符:4u KQUEUE

服务端每个连接一个 goroutine,在 Read 里“阻塞”时实际是把这个描述符登记到轮询器(macOS 上是 kqueue,Linux 上是 epoll),然后 goroutine 挂起,线程去跑别的 goroutine;数据到达时轮询器唤醒它。goroutine 的状态显示为 [IO wait],而不是占着线程的 [syscall]。进程里只有一个 kqueue 描述符。线程从 5 涨到 12 是调度与别的原因,我没有逐个解释这 7 个线程。

绕开轮询器就占线程

实测 · 本机执行输出
  200 个 goroutine 各自从 os.Pipe 的读端 Read(阻塞):线程创建数 5 → 6;状态 [IO wait]×200 [running]×1
  200 个 goroutine 各自对阻塞的 pipe 描述符直接 syscall.Read:线程创建数 5 → 203;状态 [running]×1 [syscall]×200

对照实验里,把 pipe 描述符直接交给 syscall.Read(阻塞模式),每个阻塞的调用占住一个线程(线程数 +198);而 os.Pipe() 返回的 os.File 显然走了轮询器(状态是 [IO wait],我没有读 os 包里把描述符设为非阻塞并注册的代码),同样的 200 个阻塞读只多一个线程。结论:想让大量阻塞 IO 便宜,要用 net 与 os.File 这类封装,而不是自己 syscall。普通磁盘文件不能被轮询,读写它们仍然是阻塞系统调用(本文没有试)。

超时

实测 · 本机执行输出
  SetReadDeadline(100ms) 之后 Read:101 ms 后返回,错误:read tcp 127.0.0.1:64775->127.0.0.1:64774: i/o timeout;是超时错误?true

SetReadDeadline 把超时时刻交给运行时的定时器;到点后阻塞的 Read 返回 i/o timeout,错误实现了 net.Error 且 Timeout() 为真。实测 100 ms 的超时 101 ms 后返回。

分步演示

网络轮询器
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。macOS 上网络轮询器用 kqueue(lsof 里的 KQUEUE 描述符是实测),Linux 上是 epoll,没有测;线程数用 pprof 的 threadcreate 计数(创建过的线程总数,不是当前线程);goroutine 状态来自 pprof goroutine profile 的 debug=2 标题行;所有连接在同一进程内的本地回环上
    • 没有验证:Linux 上的 epoll 行为、轮询器内部结构(netpollWaiters、边缘触发等)、普通文件与终端的读写是否走轮询器、百万连接时 kqueue 的扩展性、SetReadDeadline 在高并发下的精度、5 → 12 中新增的 7 个线程的来源
    京ICP备2026067502号-1京公网安备11010502063487号