跳到主要内容

netpoll 网络模型

问题

Go 的网络 IO 底层是怎么实现的?为什么 goroutine 阻塞在 Read 上不会浪费 OS 线程?

答案

Go 的网络 IO 模型

Go 网络 IO 的独特之处:代码看起来是同步阻塞的,底层却是异步非阻塞的

核心机制

  1. 所有网络 fd 设置为非阻塞模式
  2. Read/Write 时如果未就绪,goroutine 被 gopark 挂起(不占 OS 线程)
  3. runtime 的 netpoll 使用操作系统的 IO 多路复用:
    • Linux: epoll
    • macOS: kqueue
    • Windows: IOCP
  4. 数据就绪时,netpoll 通过 goready 唤醒对应 goroutine

与其他语言对比

模型代表开发体验性能
同步阻塞 + 线程池Java BIO简单差(线程重)
异步非阻塞 + 回调Node.js / Netty复杂(回调地狱)
异步非阻塞 + async/awaitRust tokio较好极好
同步写法 + 异步执行Go最简单

Go 的方案本质上和 Reactor 模型一样高效,但开发者不需要处理回调、Future、async/await 等复杂概念。


常见面试问题

Q1: Go 用的是 epoll 的 ET 还是 LT 模式?

答案:Go 使用 ET(Edge Triggered)边缘触发模式。这意味着每个事件只通知一次,Go runtime 需要一次性读/写完所有数据。ET 模式减少了 epoll_wait 的系统调用次数,性能更好。

Q2: goroutine 阻塞在网络 IO 时线程会怎样?

答案:goroutine 调用 gopark 被挂起,让出 M(OS 线程)给其他 goroutine 使用。当数据就绪时,goroutine 被放回调度队列等待运行。整个过程中 OS 线程不会被浪费。

相关链接