为什么多路复用要搭配非阻塞I/O

问题

I/O多路复用之epoll一文简单介绍了I/O多路复用模型。I/O多路复用的API函数返回就绪(ready)的文件描述符,在这些文件描述符上执行readwrite都不会阻塞。那么即使描述符是默认的阻塞方式,调用读写函数应该也不会阻塞。

但实际情况是,I/O多路复用一般和非阻塞I/O配合使用,比如libevent网络库所采用的Reactor模式。实际上,如果多路复用不搭配非阻塞I/O,在下面几种情况下会出现问题。

边沿触发

I/O多路复用之epoll一文有描述一种情况:当使用epoll的边沿触发模式时,必须要将描述符设置为非阻塞。

多进程

当多个进程(或线程)在同一个描述符上执行I/O时,从单个进程的视角看,在收到描述符就绪的通知和接下来的I/O调用之间这段时间内,描述符的就绪状态有可能改变。所以,如果描述符是默认的阻塞方式,I/O操作有可能会阻塞,使得进程不能够监测其它描述符。

写大量数据

即使是边沿触发模式的API如selectpoll通知字节流套接字(stream socket)可写,如果在一次writesend调用中写足够多的数据,调用依然有可能阻塞。

内核bug

在极少的情况下,边沿触发模式的API如selectpoll可能返回错误的就绪状态通知。例如Linux手册关于select的内容中有如下说明:

Under Linux, select() may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks. This could for example happen when data has arrived but upon examination has wrong checksum and is discarded. There may be other circumstances in which a file descriptor is spuriously reported as ready. Thus it may be safer to use O_NONBLOCK on sockets that should not block.

正如上面所说的,使用非阻塞I/O,程序会更加健壮。

参考

  1. The Linux Programming Interface
  2. 知乎问题:为什么IO多路复用要搭配非阻塞IO?
  3. select(2) - Linux manual page