每日一Go-41、高性能网络编程模型--epoll / netpoll / I/O多路复用

0 阅读3分钟

一、网络编程在等什么?

所有网络程序,CPU实际只做三件事:

  • 计算

  • 等待数据到来

  • 在等待的时候不要浪费CPU

高性能网络编程的本质是:用尽可能少的线程,管理尽可能多的连接,并且不空转。(让少量线程,像电话接线员一样,同时服务成千上万条连接)

二、从最原始模型开始

    2.1 阻塞I/O(一个连接一个线程)

conn -> read() 阻塞 -> 数据 -> 处理

    优点:模型简单

    问题:线程=内存+调度成本,C10K直接爆炸

C10K 是 IT 界的一个著名术语,它的全称是 Client 10,000 问题。
简单来说,C10K 问题就是:“如何让服务器同时处理 10,000 个并发连接?”

    2.2 非阻塞+轮询(CPU杀手)

while true:
  for conn in conns:
    read()

    优点:无

    问题:大量空轮询,CPU100%,吞吐却不高。

三、I/O多路复用:一次等很多

3.1 核心思想

把等I/O这件事交给内核,应用只问一句:“哪些fd限制可以读写了?”

3.2 select/poll/epoll对比

机制特点问题
select跨平台fd数量有限,O(n)
poll无fd上限仍是O(n)
epoll事件驱动Linux专属   

四、epoll:Linux的终极答案

4.1 epoll三件套

epoll_create  创建一个epoll实例,新建就绪表
-> epoll_ctl  ADD、DEL、MOD 管理列表,埋回调,把事件丢到就绪表里
-> epoll_wait 不需要遍历,利用回调机制,谁有数据就在就绪链表里

4.2 epoll为什么快?

  • 事件驱动,不是轮询

  • 就绪fd(文件描述符)由内核维护

  • O(1)级别唤醒

epoll不关心你有多少连接,只关心谁准备好了。

4.3 LT vs ET

模式特点
LT(水平触发)没读完会一直通知
ET(边缘触发)只在状态变化时通知

Go runtime使用的是LT思路 + 自己控制读取节奏

五、Go的答案:netpoll

netpoll是什么?netpoll是Go runtime内置的I/O多路复用抽象层

  • Linux -> epoll

  • macOS -> kqueue

  • Windows -> IOCP

你写的Go网络程序

conn.Read()

背后并不是“阻塞线程”,而是:

goroutine 先休息(park)
线程去干别的
epoll 监听这个 socket
数据一来
把 goroutine 叫醒

六、Go网络模型全景图

          ┌─────────────┐
          │ goroutine   │
          │ Read/Write  │
          └─────┬───────┘
                │
        ┌───────▼────────┐
        │   netpoller    │  ← epoll_wait
        └───────┬────────┘
                │
        ┌───────▼────────┐
        │ OS epoll/kq    │
        └────────────────┘

七、关键机制:goroutine如何“不阻塞”?

读写流程简化版

  • fd设置为non-blocking

  • 读不到数据-> EAGAIN

  • goroutine park

  • fd注册到netpoll

  • epoll事件到来

  • goroutine被唤醒继续执行

阻塞的是goroutine,不是OS线程

八、为什么Go不让你直接用epoll?

  • 1 跨平台:epoll/kqueue/IOCP语义不同

  • 2 调度器深度绑定:netpoll和GMP强耦合

  • 3 防止误用:99%的手写epoll都会和调度器打架

九、Go网络高性能的真正来源

不是epoll本身,而是:

epoll+goroutine的解耦

M:N 调度模型

极低的goroutine成本

I/O与调度的深度融合

十、常见误区

10.1 每个连接一个goroutine会不会很多?

答:goroutine不等于thread,goroutine初始栈2KB,调度代价极低

10.2 Go网络慢?

答:慢的通常是:业务逻辑、GC压力、锁竞争、syscall滥用

十一、工程实践建议

连接数大-> 减少per-conn状态

少用大锁,多用分片

I/O密集型服务 -> Go非常合适

CPU密集型 -> 注意GOMAXPROCS

十二、总结

Go的高性能网络,不是你写了epoll,而是你“不用写epoll”

人生不是拼命做更多事,而是学会等待真正该你出手的时刻。


微信搜索“加班计”.png 加班计算器

*源码地址*

1、公众号“Codee君”回复“每日一Go”获取源码

2、pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请您(点赞、分享、亮爱心),万分感谢!