首先先把这个词拆开看,I/O是指网络I/O;多路,是指多个TCP链接或者channel,当发生多个网络请求时,I/O多路就产生了。而复用,是指复用一个或少量线程,比如单进程的Redis,所有的请求都需要复用一个进程。所以这个词串起来理解就是:很多个网络I/O复用一个或少量的线程来处理这些连接。它还有个别称,叫“事件驱动”,就和字面意思一样,就是需要动的时候有人叫你,不叫你的时候你就躺着就行。
  目前支持多路复用的系统调用有select、poll、epoll。

  • select
    监视多个文件句柄的状态变化,程序会阻塞在select处等待,直到有文件描述符就绪或超时。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

      可以看出select可以监听fd_set *readfds, fd_set *writefds, fd_set *exceptfds三种描述符,
缺点:每次调用select,都需要把待监控的fd集合从用户态拷贝到内核态,当fd很大时,开销很大。
每次调用select,都需要轮询一遍所有的fd,查看就绪状态。
select支持的最大文件描述符(fd)数量有限,默认是1024。

  • poll
    和select没有很大区别,改进成了基于链表的,所以没有最大fd数量限制。
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
 
struct pollfd
{
    short events;
    short revents;
};

pollfd结构包括了events(要监听的事件)和revents(实际发生的事件)。而且也需要在函数返回后遍历pollfd来获取就绪的描述符。

  • epoll
    针对上面两位的共同缺点做了改进,
    select 和 poll 监听文件描述符list,进行一个线性的查找 O(n);
    epoll: 使用了内核文件级别的回调机制O(1);
    于是epoll模型在处理大量fd的时候,就和之前的模型有了数量级上的进步。
    看看这张图,epoll本身几乎是不受并发数的影响的。
    image.png
    /proc/sys/fs/epoll/max_user_watches这个文件表示用户能注册到epoll实例总最大fd的数量,我随便找了台服务器看了眼,790999,貌似够大了。所以在Linux平台,需要并发的场景下,epoll应该已经代替了select和poll。

❓那么问题来了,既然这么好用的东西出现了,select和poll还用在哪里呢?

  1. 管理少量连接(比如fd数 < 10),poll/select 是比较轻量级的,不需要去创建一个epoll的fd。而且,select实现起来应该比epoll简单不少。
  2. 编写跨平台代码,保证代码任意平台都能用,那么 cygwin 下只有 poll 给你,win32下对应 select。