Wang-Kai/cherish-today

IO 多路复用 – SELECT VS POLL VS EPOLL

Opened this issue · 0 comments

Linux 系统上一切皆文件,对于一个 Web Server 来讲,它要持续监听所有的 socket 文件,以便知道是否有新的请求。

如果我们为每一个文件创建一个线程,来监听其变动,对于文件特别多的时候,是一个行不通的方式。这时就需要用到 IO Multiplexing,其基本原则是:用一个线程来监控多个 fd,如果有一个 fd 可用,该线程就触发回调执行相应操作。

当前 IO Multiplexing 主要有三种方式 selectpoolepool

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()

  1. select()poll() 更具备通用性
  2. 比之于 select()poll() 的 output 可重用性更高一些,poll() 仅需调整 event & revent 即可。

epoll()

相比于 select() & poll() 把好多工作放在用户态做,每次整理需要监听的 fds,然后 wait,接着再根据响应整理 input 参数。epoll() 把许多任务都放到了内核态,相比来讲用户调用起来更方便。

epoll() 核心需要有 3 步:

  1. int epoll_create(int size); 创建上下文
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 添加或者删除 fd
  3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 监听等待

用户调用 epoll,主要需要如下步骤:

  1. 通过 epoll_create 创建 epoll descriptor
  2. 通过 epoll_ctl 做 add/remove/modify fd
  3. 调用 epoll_wait ,将会把 pending 的 fds 放入到队列中

Epoll vs Select/Poll

  1. 可以在 waiting 的时候添加或删除 fd
  2. epoll 可以有更好的性能,O(1) 而不是 O(n)
  3. epoll_wait 仅返回 ready 状态 fd
  4. select() 方法 fd 有数量限制,因为每次调用都需要把 fd_set 从用户态 COPY 到内核态,是很耗费性能的

总结

select()poll()epoll() 都是 IO 多路复用的具体实现,大家都可以完成任务,只是不同时代的产物而已。