阻塞io

指进程发起调用后,会被挂起(阻塞),直到收到数据再返回。如果调用一直不返回,进程就会一直被挂起。

非阻塞io

不将进程挂起,调用时立即返回成功或错误,可以在一个线程中轮询多个文件描述符是否就绪

io多路复用

遍历集合中的每个文件描述符,判断是否就绪

select

int select(int nfds,
            fd_set *restrict readfds,
            fd_set *restrict writefds,
            fd_set *restrict errorfds,
            struct timeval *restrict timeout);

select 会遍历每个集合的前 nfds 个描述符,分别找到可以读取、可以写入、发生错误的描述符,统称为“就绪”的描述符。然后用找到的子集替换参数中的对应集合,返回所有就绪描述符的总数。timeout 参数表示调用 select 时的阻塞时长。如果所有文件描述符都未就绪,就阻塞调用进程,直到某个描述符就绪,或者阻塞超过设置的 timeout 后,返回。如果 timeout 参数设为 NULL,会无限阻塞直到某个描述符就绪;如果 timeout 参数设为 0,会立即返回,不阻塞。

  1. select最大的缺陷就是单个进程所打开的FD是有一定限制的,它由FD_SETSIZE设置,默认值是1024;
  2. 每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大;
  3. 每次 kernel 都需要线性扫描整个 fd_set,所以随着监控的描述符 fd 数量增长,其 I/O 性能会线性下降;

epoll

epoll的监听列表使用红黑树存储,epoll_ctl 函数添加进来的 fd 都会被放在红黑树的某个节点内,而红黑树本身插入和删除性能比较稳定,时间复杂度 O(logN),并且可以存储大量的的fd,避免了只能存储1024个fd的限制;

epoll_ctl 中为每个文件描述符指定了回调函数,并在就绪时将其加入到就绪列表,因此不需要像select一样遍历检测每个文件描述符,只需要判断就绪列表是否为空即可;

判空O(1),判所有O(N)