IO模型:同步、异步、阻塞、非阻塞

前言:

在Linux的网络编程中,同步IO(synchronous IO)、异步IO(asynchronous IO)、阻塞IO(blocking IO)、非阻塞IO(non-blocking IO)究竟是什么?它们之间又有什么联系和区别? 本文是我对这个问题的答案整理的笔记,参考《UNIX网络编程.卷1》

一、IO模型

在《UNIX网络编程.卷1》第6.2节介绍了五种IO模型,分别是:

  • 阻塞式IO(blocking IO)
  • 非阻塞式IO(non-blocking IO)
  • IO复用(IO multiplexing)
  • 信号驱动式IO(signal driven IO)
  • 异步IO(asynchronous IO)

通常一个 socket 上的读操作包含两个阶段:

  1. 等待数据准备好;
  2. 将数据从内核拷贝到进程中。

上述几种IO模型就是在这两个阶段上各有不同的情况。


1.1 阻塞式IO

默认情况下,Linux下的所有socket都是阻塞的。以 UDP 的recvfrom调用为例:

当进程调用recvfrom时,该函数直到①数据报到达且被复制到应用进程缓冲区;②或者发生错误(比如被信号中断)才返回。

所以,阻塞式IO的特点就是在I/O执行的两个阶段都被阻塞了——阻塞等待数据,阻塞拷贝数据。


1.2 非阻塞式IO

Linux下可以通过fcntl将 socket 设置为非阻塞模式。

当对一个非阻塞 socket 执行读操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个EWOULDBLOCK错误;如果内核中有数据准备好了,它会立即将数据拷贝到用户内存,并成功返回。

由于非阻塞I/O在没有数据时会立即返回,故用户进程通常需要循环调用recvfrom,不断地主动询问内核数据是否ready。

所以,非阻塞式IO的特点是在I/O执行的第一个阶段不会阻塞线程,但在第二阶段会阻塞。


1.3 IO复用

IO复用(IO multiplexing),也称事件驱动IO(event-driven IO),就是在单个线程里同时监控多个套接字,通过 select 或 poll 轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

可以看出,进程阻塞在select调用上,等待有套接字变为可读;当有套接字可读以后,调用recvfrom把数据报从内核复制到用户进程缓冲区,此时进程阻塞在IO执行的第二个阶段。

如上图整个用户进程其实是一直被阻塞的,但IO复用的优势在于可以等待多个描述符就绪。

所以,IO复用的特点是进行了两次系统调用,进程先是阻塞在 select/poll 上,再是阻塞在读操作的第二个阶段上。


1.4 信号驱动式IO

信号驱动式IO(signal-driven IO),就是让内核在描述符就绪时发送SIGIO信号通知用户进程。

首先需要开启 socket 的信号驱动式IO功能,然后通过sigaction系统调用注册SIGIO信号处理函数 —— 该系统调用会立即返回。当数据准备好时,内核会为该进程产生一个SIGIO信号,这时就可以在信号处理函数中调用 recvfrom 读取数据了。

所以,信号驱动式IO的特点就是在等待数据ready期间进程不被阻塞,当收到信号通知时再阻塞并拷贝数据。


1.5 异步IO

异步IO(asynchronous IO)其实用得很少,在Linux 2.5 版本的内核中首次出现,在 2.6 版本的内核中才成为标准特性。

用户进程在发起aio_read操作后,该系统调用立即返回 —— 然后内核会自己等待数据ready,并自动将数据拷贝到用户内存。整个过程完成以后,内核会给用户进程发送一个信号,通知IO操作已完成。

异步IO与信号驱动式IO的主要区别是:信号驱动式IO是由内核通知我们何时启动一个IO操作,而异步IO是由内核通知我们IO操作何时完成。

所以,异步IO的特点是IO执行的两个阶段都由内核去完成,用户进程无需干预,也不会被阻塞。


1.6 五种IO模型的比较

可以看出,前4种模型的主要区别在于第一阶段,因为它们的第二阶段是一样的:都是阻塞于recvfrom调用,将数据从内核拷贝到用户进程缓冲区。


二、阻塞vs非阻塞,同步vs异步

回到本文开头的那个问题:同步IO、异步IO、阻塞IO、非阻塞IO究竟是什么?它们之间又有什么联系和区别?

阻塞IO vs 非阻塞IO

上面介绍阻塞式IO模型、非阻塞式IO模型时已经说明了两者的区别:

  • 阻塞I/O会一直阻塞用户进程直到操作完成
  • 非阻塞I/O在内核的数据还没准备好的情况下会立即返回

同步IO vs 异步IO

POSIX是这样定义的:

  • A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes. —— 同步IO操作导致进程阻塞,直到IO操作完成。
  • An asynchronous I/O operation does not cause the requesting process to be blocked. —— 异步IO操作不导致进程阻塞。

上面定义中的I/O operation是指真正的I/O系统调用,比如recvfrom,所以阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型、信号驱动式I/O模型都属于同步I/O。—— 只有异步I/O模型属于POSIX定义的异步I/O,因为在异步I/O模型中,用户进程是将整个I/O操作都交给内核来完成,内核完成后发信号通知,在此期间用户进程完全不用去理会。