# 说一下select,poll和epoll
# 简要回答
select、poll和epoll都是Linux系统中用于I/O多路复用的机制,允许单个线程监视多个文件描述符的就绪状态。
select是最早的实现,有文件描述符数量限制;
poll改进了select的局限性,但性能在大规模连接时仍不理想;
epoll是Linux特有的高性能实现,使用事件驱动模型,适合处理大量并发连接。
# 详细回答
- 1.select:
最早出现的I/O多路复用机制
使用位图(fd_set)表示文件描述符集合
默认限制为1024个文件描述符(FD_SETSIZE)
每次调用需要重新设置监控集合
采用轮询方式检查就绪状态,时间复杂度O(n)
- 2.poll:
改进select的文件描述符数量限制
使用pollfd结构数组代替位图
没有最大文件描述符数量的硬性限制
仍然需要遍历所有描述符检查就绪状态
每次调用需要复制整个描述符数组到内核
- 3.epoll:
Linux特有的高性能I/O多路复用机制
使用事件驱动模型,无需轮询
支持边缘触发(ET)和水平触发(LT)模式
使用红黑树存储监控的描述符,高效管理大量连接
就绪列表直接返回已就绪的描述符,时间复杂度O(1)
使用mmap加速内核与用户空间的数据交换
# 代码示例
// select 示例
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;
int ret = select(sockfd+1, &readfds, NULL, NULL, &tv);
if (ret > 0) {
if (FD_ISSET(sockfd, &readfds)) {
// 处理可读事件
}
}
// poll 示例
struct pollfd fds[1];
fds[0].fd = sockfd;
fds[0].events = POLLIN;
int ret = poll(fds, 1, 5000);
if (ret > 0) {
if (fds[0].revents & POLLIN) {
// 处理可读事件
}
}
// epoll 示例
int epfd = epoll_create1(0);
struct epoll_event ev, events[10];
ev.events = EPOLLIN;
ev.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
int nfds = epoll_wait(epfd, events, 10, 5000);
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == sockfd) {
// 处理可读事件
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 知识拓展
- 三者性能对比:
连接数少时,三者性能差异不大
连接数超过1000时,select/poll性能线性下降,epoll性能基本不受连接数影响
- 适用场景:
select/poll: 跨平台程序,连接数少
epoll: Linux平台高并发服务
- 水平触发(LT)和边缘触发(ET)
ET: 只在状态变化时通知一次,必须一次性处理完所有数据
LT: 只要满足条件就持续通知,可以分多次处理
- 知识图解

- 面试官可能追问
Q1.为什么epoll比select/poll更高效?
epoll使用事件驱动而非轮询,只关注活跃连接
epoll使用红黑树管理描述符,查找效率高
epoll_wait直接返回就绪列表,无需遍历所有连接
内核与用户空间共享内存,减少数据拷贝
Q2.epoll的LT和ET模式有什么区别?如何选择?
LT(水平触发): 只要fd就绪就会不断通知
ET(边缘触发): 只在fd状态变化时通知一次
ET效率更高但编程更复杂,需要一次处理完所有数据
默认使用LT,高性能场景使用ET
Q3.epoll是如何实现高性能的?
使用红黑树存储监控的fd,插入删除效率高
就绪列表直接返回活跃fd,无需遍历
使用mmap共享内存减少内核与用户空间数据拷贝
回调机制避免轮询
Q4.select的1024限制可以修改吗?
可以修改FD_SETSIZE宏并重新编译程序
但不推荐,因为内核可能有自己的限制
需要大文件描述符数量时应改用poll/epoll
← 互斥锁与自旋锁
评论
验证登录状态...