select vs. epoll:高性能I/O多路复用的核心对比

399 阅读2分钟

一、核心原理与性能瓶颈

selectepoll 都是 Linux 下实现 I/O 多路复用(I/O Multiplexing)的机制,它们允许单个线程同时监听多个文件描述符(如 Socket)。

特性selectepoll
工作模式轮询(Polling) 。每次调用都需要遍历所有文件描述符。事件驱动(Event-driven) 。内核在事件发生时通过回调通知。
时间复杂度O(N) 。性能随着文件描述符数量 N 的增加而线性下降。O(1) 。性能不受文件描述符数量影响。
文件描述符限制默认最多只能监听 1024 个文件描述符。几乎无限制。
内存拷贝每次调用都需要将整个文件描述符集合从用户空间拷贝到内核空间。只在添加文件描述符时进行一次拷贝。

二、Android中的I/O多路复用

epoll 机制在 Android 系统中扮演着至关重要的角色,特别是在主线程的消息循环中。

1. 主线程的Looper

  • Android 主线程的 Looper 并非一个简单的“死循环”。它在没有消息时,会调用 epoll_wait 方法,让主线程进入休眠状态,从而不占用 CPU。
  • 当有新的事件到来时(如触摸事件、Handler 消息),epoll 会被触发,唤醒 Looper,使其能够及时处理消息。

2. 高性能网络库

  • OkHttp、Retrofit 等现代网络库,其底层依赖于 Java 的 NIO(非阻塞 I/O)
  • 在 Linux 系统上,NIO 的 Selector 机制在底层就是通过 epoll 实现的。Selector 允许单线程同时监听多个 Socket 连接,从而实现高效的并发网络通信。

3. 进程间通信(Binder)

  • Binder 驱动内部同样利用了 epoll 机制。Binder 线程在没有任务时,会通过 epoll_wait 进入休眠,等待来自其他进程的 Binder 请求。

三、API设计与工作流程

selectepoll 的 API 设计反映了它们不同的工作模式。

1. select 的设计

  • 开发者需要创建一个 fd_set(文件描述符集合)。
  • 每次调用 select(),都需要传入这个完整的 fd_set,并让内核对其进行轮询。

2. epoll 的设计

  • epoll_create() :创建一个 epoll 实例。
  • epoll_ctl() :通过 EPOLL_CTL_ADDEPOLL_CTL_MODEPOLL_CTL_DEL 等命令,动态地添加、修改和删除文件描述符。
  • epoll_wait() :等待事件发生。当有事件就绪时,它会返回就绪的文件描述符列表,而不是像 select 那样返回所有文件描述符。