阻塞还是非阻塞,同步还是异步

在找工作时,经常被面试官问起网络IO五种模型、同步异步、阻塞非阻塞之类的问题,尤其是字节跳动面试时被当做一道大题进行了次全面的分析。但是我始终没有彻底的研究过,由于疫情影响不得不宅在家里,在学习时无意想起这个知识点,便深入的学习整理了一下。

因将要使用Linux开发,故这里所说的为Linux下在进行网络通信时,常用的IO模型。

写在前面

IO过程分为两个步骤

  1. 内核准备好数据
  2. 应用程序从内核空间将数据复制到用户空间

根据网络检索的POSIX定义,同步异步的区别在于第二个步骤是否由应用程序完成。在我自己个人看来,同步异步的主要区别在于做这个IO的时机是否是应用程序可控的。

同步在我们平常的术语解释中一般应理解为做事情的先后顺序是有序可循的,而异步时并不能确定对方做某件事的时机。具体到linux下的网络IO模型中来,我认为第二个步骤是否由应用程序完成,就代表着应用程序是否能控制真正IO进行的时机,也就代表着应用程序的执行流程是否是可控的。

五种IO模型

阻塞

应用程序在进行阻塞IO时,程序暂停运行,等待内核将数据准备完毕并通知应用程序,应用程序唤醒并从内核将数据读取到用户空间。

程序停止运行,等待内核准备数据,所以叫做阻塞。应用程序自己控制数据从内核的读取,因此阻塞方式为同步的。

非阻塞

非阻塞IO模式时,应用程序会不停地去询问内核数据是否准备完成,类似轮询的方式。非阻塞应用程序在内核未准备完成数据时,会返回失败。当内核准备完成数据,应用程序再去询问时就会开始处理数据。

在非阻塞IO模型里,应用程序每次在询问之后,回去处理别的事情,程序不会阻塞在IO上, 因此是非阻塞的方式。同样在处理数据时是应用程序自行处理的,所以也为同步模型。

IO复用

IO复用这个概念用于解决单一进程处理多个文件描述符的情况,不单单可以解决网络IO复用的问题。

IO复用模型会同时监测多个文件描述符的IO请求。执行时会阻塞在这里,当任一文件描述符数据已准备好时,内核会通知应用程序,有应用程序来进行IO。

在IO复用相关函数执行时,应用程序不会执行其他的程序,因此IO复用模型是阻塞的。当数据准备好时,由应用程序来处理IO操作,这是整个程序也是阻塞到IO操作上,所以IO复用模型为同步模型。

信号驱动IO

信号是Linux系统处理突发事件的一种方式,我们可以通过设置信号处理函数来接收其他程序或系统发送的信号,我们当然也可以选择忽略。

信号驱动IO模型在设置了即将处理的信号的信号处理函数之后,就可以去做别的工作了。当内核准备好数据后,会使用信号的方式通知应用程序,应用程序便会启用信号处理函数来处理IO操作。

信号驱动方式在等待内核准备数据时可以去处理别的工作,因此信号驱动IO模式是非阻塞的。但是在收到内核发送的信号之后,IO操作仍然需要应用程序来完成,因此信号驱动IO模型任然为同步模型。

异步IO

应用程序使用异步IO相关的API时,会同时告知内核IO操作的目的缓存,将真正的IO操作也交给了内核。内核在完成数据准备和IO操作后,会发送信号告知应用程序。

因为应用程序不需要去做真正的IO,一切操作都交给了内核,因此为异步模型。

总结

上述五种IO模型为Linux下的IO模型,在《UNIX环境高级编程》这本书的6.2节有介绍。

其中前四种都为同步模型,其中既有阻塞模型也有非阻塞模型。只有异步IO模型为真正的异步模型。

同步和异步的区别就在于真正的IO由谁来完成。