一、核心原理与性能瓶颈
select 和 epoll 都是 Linux 下实现 I/O 多路复用(I/O Multiplexing)的机制,它们允许单个线程同时监听多个文件描述符(如 Socket)。
| 特性 | select | epoll |
|---|---|---|
| 工作模式 | 轮询(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设计与工作流程
select 和 epoll 的 API 设计反映了它们不同的工作模式。
1. select 的设计
- 开发者需要创建一个
fd_set(文件描述符集合)。 - 每次调用
select(),都需要传入这个完整的fd_set,并让内核对其进行轮询。
2. epoll 的设计
epoll_create():创建一个epoll实例。epoll_ctl():通过EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL等命令,动态地添加、修改和删除文件描述符。epoll_wait():等待事件发生。当有事件就绪时,它会返回就绪的文件描述符列表,而不是像select那样返回所有文件描述符。