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 小时前
Plugin ‘mysql_native_password‘ is not loaded`
java·后端
故事不长丨4 小时前
【Java SpringBoot+Vue 实现视频文件上传与存储】
java·javascript·spring boot·vscode·后端·vue·intellij-idea
晟盾科技4 小时前
报表类系统后端API设计思路
开发语言·windows·php
AI纪元故事会4 小时前
冰泪与雨丝:一个AI的Python挽歌
开发语言·人工智能·python
9ilk5 小时前
【仿RabbitMQ的发布订阅式消息队列】--- 前置技术
分布式·后端·中间件·rabbitmq
我不是程序猿儿5 小时前
【C#】WinForms 控件句柄与 UI 刷新时机
开发语言·ui·c#
十五年专注C++开发5 小时前
Qt-Nice-Frameless-Window: 一个跨平台无边框窗口(Frameless Window)解决方案
开发语言·c++·qt
凯歌的博客5 小时前
python虚拟环境应用
linux·开发语言·python
鬼火儿6 小时前
Golang笔记——Interface类型
java·后端