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 的"同步写法 + 异步模型"特征。

相关推荐
7***37452 分钟前
Java设计模式之工厂
java·开发语言·设计模式
雨中飘荡的记忆4 分钟前
ByteBuddy 实战指南
后端
Apifox10 分钟前
Apifox 11 月更新|AI 生成测试用例能力持续升级、JSON Body 自动补全、支持为响应组件添加描述和 Header
前端·后端·测试
上不如老下不如小17 分钟前
2025年第七届全国高校计算机能力挑战赛初赛 Python组 编程题汇总
开发语言·python·算法
有风6323 分钟前
双向循环带头链表详解
后端
程序员小白条27 分钟前
你面试时吹过最大的牛是什么?
java·开发语言·数据库·阿里云·面试·职场和发展·毕设
找不到对象就NEW一个31 分钟前
用wechatapi进行微信二次开发,微信api
后端
charlie11451419131 分钟前
勇闯前后端Week2:后端基础——Flask API速览
笔记·后端·python·学习·flask·教程
有风6337 分钟前
基于顺序表完成通讯录项目
后端
yuuki23323338 分钟前
【C++】初识C++基础
c语言·c++·后端