1. 非阻塞读取的核心概念
当我们需要从文件、网络套接字或其他I/O设备读取数据时,传统的阻塞式读取会让程序"卡住"等待,直到数据就绪。这种同步I/O模型在需要同时处理多个任务的场景下效率极低。非阻塞读取则允许程序在数据未就绪时立即返回,继续执行其他任务。
举个生活中的例子:阻塞式读取就像在快餐店点餐时,必须站在柜台前一直等到餐点做好才能离开;而非阻塞读取则是先下单,然后可以去座位上玩手机,等餐好了再去取。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流非阻塞读取方法解析
2.1 轮询(Polling)机制
轮询是最基础的非阻塞实现方式,程序定期检查I/O设备是否就绪。在Linux系统中,我们可以使用fcntl函数设置文件描述符为非阻塞模式:
c复制int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
设置后,read调用会立即返回。如果数据未就绪,会返回-1并设置errno为EAGAIN或EWOULDBLOCK。
注意:过度轮询会浪费CPU资源。通常需要配合适当的休眠时间,比如每次检查后sleep(10ms)。
2.2 I/O多路复用技术
2.2.1 select系统调用
select允许程序监控多个文件描述符,当任一描述符就绪时返回:
c复制fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
struct timeval timeout = {.tv_sec = 5, .tv_usec = 0};
int ready = select(fd+1, &readfds, NULL, NULL, &timeout);
if (ready > 0 && FD_ISSET(fd, &readfds)) {
// 执行读取操作
}
select的缺点:
- 文件描述符数量有限(FD_SETSIZE通常为1024)
- 每次调用需要重新设置监控集合
- 线性扫描所有描述符效率低
2.2.2 poll系统调用
poll改进了select的一些限制:
c复制struct pollfd fds[1];
fds[0].fd = fd;
fds[0].events = POLLIN;
int ready = poll(fds, 1, 5000); // 5秒超时
if (ready > 0 && (fds[0].revents & POLLIN)) {
// 执行读取操作
}
优势:
- 无文件描述符数量硬限制
- 更精细的事件控制
- 不需要每次重建监控集合
2.2.3 epoll(Linux特有)
epoll是Linux下高性能的I/O事件通知机制:
c复制int epfd = e
