2026/5/21 18:47:17
网站建设
项目流程
网站建设推广找stso88效果好,wordpress默认amp,佛山新网站建设平台,企业微信小程序制作文章目录引言Select#xff1a;早期的探索Poll#xff1a;Select 的链表版Epoll#xff1a;Linux 的杀手锏Java和Go有什么需要了解的小知识#xff1f;总结引言
作为一个 Java 和 Go 后端开发者#xff0c;深刻理解 IO 多路复用是掌握高性能网络编程#xff08;如 Netty…文章目录引言Select早期的探索PollSelect 的链表版EpollLinux 的杀手锏Java和Go有什么需要了解的小知识总结引言作为一个 Java 和 Go 后端开发者深刻理解 IO 多路复用是掌握高性能网络编程如 Netty的基石。简单来说IO 多路复用是一种允许单个线程同时监视多个文件描述符FD, File Descriptor的技术。一旦某个 FD 就绪读/写就绪内核会通知应用程序进行处理。如果没有它处理 10,000 个并发连接可能需要 10,000 个线程资源消耗巨大或者使用非阻塞 IO 轮询空转烧 CPU。下面我将从底层原理对比select、poll和epoll进行说明。Select早期的探索select是最早期的 IO 多路复用实现。工作原理用户进程将需要监视的fd_set一个位图 bitmap拷贝到内核空间。内核遍历一遍所有的 socket如果有数据就标记为可读/可写。内核将fd_set拷贝回用户空间。用户进程再次遍历fd_set找到被标记的 socket 进行处理。缺点性能开销大每次调用都要把 FD 集合在用户态和内核态之间拷贝内核和用户态都需要遍历整个 FD 集合时间复杂度O ( n ) O(n)O(n)。连接数限制默认限制为1024个连接由FD_SETSIZE宏定义虽然可以改但效率会急剧下降。总的原因就是每次调用都需要拷贝fd_set而且需要进行线性循环。来个形象的比喻就是服务员需要挨个问客人你点好单了吗不用想都是效率十分低下。这张图展示了select低效的原因每次调用都需要在用户态和内核态之间拷贝整个文件描述符集合 (fd_set)并且内核和用户程序都需要进行 O(n) 的线性遍历。PollSelect 的链表版poll和select本质区别不大。工作原理它使用pollfd结构体的链表或数组而不是 bitmap 来传递 FD。改进点没有最大连接数限制受限于系统内存和文件描述符限制。缺点性能依然是瓶颈它和select一样内核需要线性遍历所有 FD 来检查状态用户态也需要遍历所有 FD 来查找谁就绪了。随着连接数增加性能线性下降最大的性能瓶颈并没有解决所以这个我们并不需要太了解。EpollLinux 的杀手锏epoll是为了解决 C10K 问题而生的它是目前 Linux 下高性能网络编程的核心。核心设计三个函数epoll_create在内核创建一个 epoll 对象内部结构是一棵红黑树和一个双向链表。epoll_ctl向红黑树中添加、删除或修改感兴趣的 FD。这也是O ( log n ) O(\log n)O(logn)的效率比线性扫描快得多。epoll_wait等待事件。工作原理Callback 机制epoll不再轮询。它为每个 FD 注册一个回调函数。当网卡接收到数据中断程序会调用回调函数将该 FD 添加到就绪链表Ready List中。epoll_wait实际上只是在检查这个就绪链表是否为空。用户进程只需要处理就绪链表中的 FD不需要遍历所有连接。优点效率极高时间复杂度为O ( 1 ) O(1)O(1)严格来说是O ( k ) O(k)O(k)k 为活跃连接数。性能不会随总连接数增加而下降只与“活跃”连接数有关。内存拷贝少使用了 mmap内存映射技术或高效的内存管理减少了复制开销注现代实现主要是避免了像 select 那样每次调用都重复传入整个 FD 集合。两种触发模式LT (Level Triggered - 水平触发)默认模式。只要缓冲区还有数据内核就会一直通知你。ET (Edge Triggered - 边缘触发)高速模式。只有数据状态发生变化从无到有时通知一次。如果你不读完内核不会再通知这要求程序必须一次性把数据读完。Go 和 Nginx 使用的是 ET 模式的变种或思想来追求极致性能。不好理解来个比喻服务员再也不用去挨个问是否点好单而是由大堂经理将需要订单的顾客名单给服务员去通知后厨这张图展示了epoll高效的原因它使用红黑树来管理所有的文件描述符只需注册一次并采用事件驱动的机制。当网络设备有数据到达时通过回调函数直接将就绪的 FD 加入到“就绪链表”中应用程序只需要处理这个链表即可无需遍历所有连接Java和Go有什么需要了解的小知识作为后端开发了解这些对你理解语言底层至关重要Java (NIO / Netty):Java 的java.nio.channels.Selector是一个抽象层。在 Linux 上JDK 会自动映射到底层的epoll。Netty的核心 EventLoop 也就是在一个线程中不断轮询这个Selector即epoll_wait实现了高性能的 Reactor 模型。Go (Goroutine Netpoller):Go 的网络编程看起来是同步阻塞的比如conn.Read()但底层完全是异步非阻塞的。Go Runtime 包含了一个Netpoller网络轮询器。在 Linux 下Netpoller 封装了epoll。当你调用conn.Read()且没有数据时Go 调度器会将该 Goroutine 挂起Gopark并将 FD 注册到epoll中。当epoll通知数据就绪Go 调度器再唤醒该 Goroutine。这就是 Go 高并发的核心秘密用同步的代码逻辑享受了 epoll 的异步性能。总结这就是全部内容下面是一个小结表格。特性SelectPollEpoll底层数据结构Bitmap (数组)链表 / 数组红黑树(存储FD) 双向链表(存储就绪FD)时间复杂度O ( n ) O(n)O(n)O ( n ) O(n)O(n)O ( 1 ) O(1)O(1)(与活跃数有关)最大连接数1024 (默认)无限制无限制 (受系统内存限制)IO效率随连接数增加而显著下降随连接数增加而显著下降不随总连接数线性下降数据拷贝每次调用都需要拷贝全部 FD每次调用都需要拷贝全部 FDFD 仅在注册时拷贝一次如果觉得我讲的好就给我点赞收藏关注吧这是我更新的最大动力❤️