Go 的 IO 多路复用

Go 的 IO 多路复用epoll vs select 差异,以及 Go runtime 中是如何使用它们的。


✅ 一、什么是 IO 多路复用?

IO 多路复用解决的问题是:

一个线程同时监听多个文件描述符(socket 等)的 IO 状态变化

不需要为每个连接创建一个线程或阻塞在一个 IO 上

常见系统调用:

  • select
  • poll
  • epoll(Linux)
  • kqueue(BSD / macOS)
  • IOCP(Windows)

✅ 二、select vs epoll 区别(重点)

对比项 select epoll
支持的 fd 数量 有上限,一般1024 理论无限(由系统内存决定)
时间复杂度 O(n) 每次扫描全部 fd O(1) 回调通知,只处理活跃 fd
内核与用户态交互 每次都复制整个 fd 集合 使用内核维护红黑树和就绪链表,无需重复传输大量数据
触发模式 只支持 水平触发 支持 水平触发 + 边缘触发(更高效)
资源消耗 大,重复构造 fd 集合 小,只在 add/del 时修改
大并发性能 优秀,适合 10w+ 连接

一句总结:

select:每次全部扫描
epoll:事件通知,只处理变化的 FD

⇒ 大并发下 epoll 优势极大


✅ 三、为什么 Go 用 epoll(Linux)?

Go 的网络模型是 goroutine + epoll/kqueue + 协程调度器

sh 复制代码
用户写代码:阻塞 I/O
↓
Go runtime:用 epoll/kqueue 监控 socket
↓
有事件就唤醒对应的 goroutine

实际上,netpoller 负责底层事件循环:

平台 多路复用机制
Linux epoll
macOS / FreeBSD kqueue
Windows IOCP

你写的看似阻塞的 conn.Read() 实际是:

  • goroutine 被 park(挂起)
  • IO 可读时,epoll 通知 runtime
  • runtime 唤醒该 goroutine

Goroutine ≠系统线程

一个线程同时调度成千上万 goroutines

✅ 所以 Go 在高并发网络服务中天生强大。


✅ 四、代码层使用体验

你写的代码不需要关心 epoll:

go 复制代码
for {
    n, err := conn.Read(buf)
    if err != nil {
        return
    }
    fmt.Printf("read %d bytes\n", n)
}

Go runtime 自动协程化。


✅ 五、什么时候 select 仍会被使用?

  • 小规模 fd 管理,简单快速
  • 跨平台兼容
  • Go 内部在 netpoll 未启用场景下 fallback

✅ 六、一句话总结

select 采用轮询,fd 数量有限且 O(n) 性能差;

epoll 采用事件回调,仅返回活跃 fd,支持更大并发;

Go 的 Runtime 在 Linux 上使用 epoll 实现 IO 多路复用,协程调度隐藏了异步 IO 的复杂性,使阻塞 API 实际是高效的事件驱动模型。

实际编码中为什么感受不到?

理解 Go 网络模型的核心

Go 代码里看不到 select / epoll,但它们的特征仍然"渗透"在你的代码行为里。

我们从 开发者视角底层表现 两个层面看。


✅ 一、Go 层看不到 epoll/select,但它们在底层驱动

代码可能长这样:

go 复制代码
for {
    conn, err := listener.Accept()
    if err != nil {
        continue
    }
    go handle(conn)
}

func handle(conn net.Conn) {
    buf := make([]byte, 1024)
    for {
        n, err := conn.Read(buf)
        if err != nil {
            return
        }
        conn.Write(buf[:n])
    }
}

看起来是同步阻塞式写法:

  • Accept() 阻塞
  • Read() 阻塞
  • Write() 阻塞

👉 但在 Go runtime 底层

  • 每个 goroutine 被 runtime 管理;
  • netpoll 使用 epoll(Linux) / kqueue(macOS) / IOCP(Windows)
  • 当 IO 不可读/写时,goroutine 被"挂起"(park);
  • 事件到达后,runtime 唤醒对应 goroutine。

所以:

阻塞 API,非阻塞执行。

这就是 Go 的"同步写法 + 异步模型"特征。

相关推荐
苏三说技术4 小时前
LangChain4j 和 LangGraph4j,哪个更好?
后端
ServBay5 小时前
7 个AI开发中真正用得上的 MCP Server,配合Claude Code食用效果更佳
后端·claude·mcp
妙码生花5 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十五):优化细节、网络请求封装
前端·后端·ai编程
用户6757049885026 小时前
Go 语言里判断字符串为空,90% 的人都写错了!
后端·go
用户6757049885026 小时前
Go 进阶必修:90% 的人都没用对的“表驱动法”
后端·go
小兔崽子去哪了6 小时前
Java 生成二维码解决方案
java·后端
苍何6 小时前
懂事的 Agent 已经开始自己看屏幕干活了,效率起飞!
后端
掘金码甲哥7 小时前
1分钟买不了吃亏系列: nginx动态域名解析
后端
神奇小汤圆7 小时前
2026大厂Java岗面试记录:八股+场景+项目+AI,一文讲透快速上岸路径(含答案)
后端