[关闭]
@FunC 2018-05-15T11:27:20.000000Z 字数 1531 阅读 1761

I/O 复用补充内容

unix


进行大量 I/O 操作时使用多(进)线程的局限性:

开销昂贵;即使是多线程,线程间的通信工作仍会让编程工作变得复杂

当然多线程也有用武之地,正如之前在 Node 的笔记中提到的,面对计算密集型(CPU-bound)任务还是用多线程处理更佳。即 event loop + thread poll

select() 和poll() 存在的问题

因为select() 和 poll() 需要检查所有关心的文件描述符的就绪状态,因此当需要检查大量的文件描述符时,其性能将大幅下降。归根到底在于程序重复调用这些借口,而内核不会在每次成功调用后记录下他们,导致了大量的重复工作。

epoll() 和信号驱动I/O 能解决这个问题。

epoll编程接口

epoll API 由以下3个系统调用组成:
* epoll_create() 创建一个epoll实例,返回代表该实例的文件描述符
* epoll_ctl() 用于操作同 epoll 实例相关联的兴趣列表(增删改)。
* epoll_wait() 返回与 epoll 实例相关联的就绪列表中的成员(关键)

下面稍微介绍一下这三个 API。

创建 epoll 实例:epoll_create()

  1. #include <sys/epoll.h>
  2. int epoll_create(int size);
  3. /* 返回:成功则返回相应的文件描述符,出错为 -1 */

返回值代表新创建的 epoll 实例的文件描述符,在后面两个 epoll 系统调用中表示对应的 epoll 实例。当这个文件描述符不再需要时,应该通过 close() 来关闭。

修改 epoll 的兴趣列表:epoll_ctl()

  1. #include <sys/epoll.h>
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
  3. /* 返回:成功时为 0,出错为 -1 */

其中参数 op 指定要执行的操作(增删改)

参数 ev 指向 结构体 epoll_event 的指针,定义如下:

  1. struct epoll_event {
  2. unit32_t event;
  3. epoll_data_t data;
  4. };

类似于 poll 中的参数,包含关联的描述符以及发生的事件

事件等待:epoll_wait()

  1. #include <sys/epoll.h>
  2. int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
  3. /* 返回:就绪的描述符数量,超时返回 0,出错返回 -1 */

其中参数 evilest 指向的结构体数组中返回有关就绪态文件描述符的信息,其空间由调用者通过参数 maxevents 申请。

边缘触发通知

默认情况下,epoll 提供的是水平触发通知(即检查文件描述符当前是否可以非阻塞执行I/O操作)。epoll 同时也支持边缘触发(即文件描述符自上次状态检查以来有了新的 I/O活动)

要使用边缘触发通知,要在调用 epoll_ctl() 时在 ev.evnts 字段中指定 EPOLLET 标志:

  1. struct epoll_event ev;
  2. ev.data.fd = fd;
  3. ev.events = EPOLLIN | EPOLLET;
  4. if (epoll+ctl(epfd, EPOLL_CTL_ADD, fd, ev) == -1)
  5. errExit("epoll_ctl");

epoll 优缺点

优点:
* 当检查大量的文件描述符时,epoll 的性能延伸性比 select() 和 poll() 高很多
* epoll API 既支持水平触发,也支持边缘触发

与信号驱动I/O相比:
* 可以避免复杂的信号处理流程(如信号队列溢出时的处理)
* 灵活性高,可以指定我们希望检查的事件类型。

缺点:
* epoll API 为 Linux 系统专有,可移植性一般(如 MacOS 使用kqueue)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注