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 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
姚不倒4 小时前
Go语言进阶:接口、错误处理与并发编程(goroutine/channel/context)
云原生·golang
candyTong4 小时前
Claude Code 的 Edit 工具是怎么工作的
javascript·后端·架构
cen__y5 小时前
Linux12(Git01)
linux·运维·服务器·c语言·开发语言·git
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题 第65题】【JVM篇】第25题:谈谈对 OOM 的认识
java·开发语言·jvm
GetcharZp5 小时前
GitHub 2.4 万 Star!D2 正在重新定义程序员画图方式
后端
社交怪人5 小时前
【算平均分】信息学奥赛一本通C语言解法(题号2071)
c语言·开发语言
郭涤生6 小时前
不同主机之间网络通信-以太网连接复习
开发语言·rk3588
山居秋暝LS6 小时前
【无标题】RTX00安装paddle OCR,win11不能装最新的,也不能用GPU
开发语言·r语言
卢锡荣6 小时前
单芯通吃,盲插标杆 —— 乐得瑞 LDR6020,Type‑C 全场景互联 “智慧芯”
c语言·开发语言·计算机外设