IO 多路复用 – SELECT VS POLL VS EPOLL
Opened this issue · 0 comments
Linux 系统上一切皆文件,对于一个 Web Server 来讲,它要持续监听所有的 socket 文件,以便知道是否有新的请求。
如果我们为每一个文件创建一个线程,来监听其变动,对于文件特别多的时候,是一个行不通的方式。这时就需要用到 IO Multiplexing
,其基本原则是:用一个线程来监控多个 fd,如果有一个 fd 可用,该线程就触发回调执行相应操作。
当前 IO Multiplexing
主要有三种方式 select
、pool
、epool
select()
select()
system call 将会阻塞,直到有 fd 是可用状态,或者超时。
#include <sys/select.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
/* Returns: positive count of ready descriptors, 0 on timeout, –1 on error */
select 有几个参数:
writefds
监控 fd 是否可以无阻塞的做写操作readfds
监控 fd 是否可以读exceptfds
监控 fd 是否出现异常
当函数成功返回时,fd_set
会被修改,其中仅包含可以做 I/O 操作的 fds
总结
select()
每次调用都需要构建每个 fd_set,并且每次都要遍历核对 fds 是否已经在 return 的集合中了,然后把它拿掉。
poll()
相比于 select()
3 个不同的 fds_set,poll() 把他们归总到了一起,并且不会破坏 input 结构,input 可以持续使用。
#include <sys/poll.h>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd {
int fd;/* file descriptor */
short events;/* requested events to watch */
short revents;/* returned events witnessed */
};
poll
定义了 pollfd 结构体,每次仅需比对 event
& revent
即可。比之于 select()
我们每次无需重新整理 input。
对比 select()
& poll()
select()
比poll()
更具备通用性- 比之于
select()
,poll()
的 output 可重用性更高一些,poll()
仅需调整event
&revent
即可。
epoll()
相比于 select()
& poll()
把好多工作放在用户态做,每次整理需要监听的 fds,然后 wait,接着再根据响应整理 input 参数。epoll()
把许多任务都放到了内核态,相比来讲用户调用起来更方便。
epoll()
核心需要有 3 步:
int epoll_create(int size);
创建上下文int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
添加或者删除 fdint epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
监听等待
用户调用 epoll
,主要需要如下步骤:
- 通过
epoll_create
创建 epoll descriptor - 通过
epoll_ctl
做 add/remove/modify fd - 调用
epoll_wait
,将会把 pending 的 fds 放入到队列中
Epoll
vs Select/Poll
- 可以在 waiting 的时候添加或删除 fd
epoll
可以有更好的性能,O(1) 而不是 O(n)epoll_wait
仅返回 ready 状态 fdselect()
方法 fd 有数量限制,因为每次调用都需要把fd_set
从用户态 COPY 到内核态,是很耗费性能的
总结
select()
、poll()
、epoll()
都是 IO 多路复用的具体实现,大家都可以完成任务,只是不同时代的产物而已。