关于 I/O 模型的理解

阻塞、非阻塞、同步、异步的概念

为什么要讨论这组概念,它的问题域是什么?

同步与阻塞的处理一般发生在socket-IO操作上,不是说常规操作没有同步与异步的区分,但只有socket-IO操作,这个问题的处理机制和结果差异被最大化了:

  • IO:如果没有IO操作,而是CPU密集型操作,那需要用到的是——多线程与多进程技术!异步操作不会有太大帮助——非IO运算的步骤之间耦合度很高,如果前一步的计算没有结果就返回了,往往带给程序的不是效率的提升,而是无尽的麻烦……
    实际上,之所以提到同步与异步的处理方式,就是因为IO与CPU的运算速度不对等——在IO操作过程中,如何利用闲置的CPU资源的问题。
  • socket:试想,如果你仅仅是read一个本地文件,即便文件的尺寸再大,使用同步的方式还是可以接受的——这个时间再长也一定会有结果,同异步的差异仅仅是效率上的差别罢了;但是问题域放到网络间,这个问题就被放大了,甚至带来功能性的影响:你的线程因为网络原因(如网络中断),不会得到正确的处理,那么这个处理程序将成为僵尸进程。这个是不可接受的!

所以才提出了一组处理机制,用于处理不同的IO模型。

于是乎,问题域就明确了——socket(或者文件IO)的客户端(主动调用方,本机socket)与被调函数(IO执行操作,服务端socket)的处理机制问题:

  • 服务端(被调函数内部的实现)决定是否阻塞;
  • 同步、异步针对的是本机socket,或者说是客户端编程(但跟服务端不是没有关系)

怎样理解阻塞与同异步的区别?

阻塞与非阻塞:我们在编写函数时,有个“返回值”的概念。函数实现如果在资源条件不满足时先返回(那么返回的一定是个无效数据),则称为“非阻塞”;而如果函数的目标是:必须返回有效数据,那么它就要等待资源全部充分,这个过程导致CPU将线程挂起,称为“阻塞”。

同步与异步:我们在编程写逻辑时,假设有若干个步骤,如果你坚持步骤间一个个的按顺序完成,称为“同步”;反之,你不需要保证前一步完成了才能完成下一步,则称为“异步”。注意:在异步调用时是不能立即得到结果的。

阻塞与非阻塞出现在资源不充分的条件下,否则没有区别。

示例

举个例子:我去书店买书——场景假设了三个步骤:

  • 走去书店
  • 买书
  • 回到家里

那么,去书店自不用说,只要有道路资源就充足了,它不涉及上面的概念;买书就有的谈了——如果书店没开门呢?如果开门了没有要买的这本书呢?……但书店有没有书,买没买成,这是“买书”这件事的子内容(我们等下再说它),跟下面“回家”这个步骤是两件事:

  • 如果不管买没买到,我都要在10分钟后(更严格的说,是在书店逛了一圈并采取了某种策略)启动“回家”这个步骤,这称为“异步”;
  • 如果没买到我就不执行“回家”这个步骤,这个称为“同步”(所以常说“同步等待”);

再回来说买书,它依赖的资源(书店开门、有这本书、这本书可以被购买)挺多,简化一下,书店开门了但没有书吧:

  • 如果买不到我们就不走,等书店给我们造一本,或现场采购来一本,我们才算完成“买书”这个步骤,那么就是“阻塞”过程;
  • 如果我们不管有没有书,买没买到,都会选择转一圈就出门,那就是“非阻塞”过程;至于怎么买——
    • 我可以留下钱和地址,让书店回头寄送给我,这个叫“回调”;
    • 我可以留下电话,让书店进货了call给我,我再来一趟书店,这个叫“通知”;
    • 我没让老板做什么,但我心里记下了,过一天还要再“回来”买一次,这个叫“轮询”;

轮询:同步非阻塞

关于买书的策略,通常称“通知”和“回调”为异步策略,“轮询”被称为同步策略(所谓“同步非阻塞”),为什么呢:

轮询实际上不是没有返回,而是在检测到资源不充分时,返回了Error状态;客户端得到了这一过程的结果(虽然是Error),但这个步骤算是执行完成了,才开始执行下一步骤。这个过程与“到书店买书,买到了,然后出门回家”的过程没有区别。故而这个是“同步”策略。

所以,上面的示例中,我们应该将轮询放在上面“同步”这个层级中,而不是异步的策略之一。

小结

同异步与阻塞可以说是两个维度上的概念:

  • 同步与否,在于多个动作步骤,是否逐一执行且每一步都得到了明确的结果;
    注意:异步的结果可不是通过返回值哦,可能是回调函数等其他手段……
  • 阻塞与否,则是函数实现过程,是否在资源不充分时立即返回。

同步能够保证程序的可靠性,而异步可以提升程序的性能。

同步与异步的实现依赖于函数的实现(是否为阻塞模式),这个在《Unix网络编程卷》中的同异步定义就有说明:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
An asynchronous I/O operation does not cause the requesting process to be blocked;

I/O模型

基本 Linux I/O 模型的简单矩阵

每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序都有自己的优点。

不同的操作系统上有不同的IO模型,《Unix网络编程卷》将unix上的IO模型分为5类:blocking I/O、nonblocking I/O、I/O multiplexing(select and poll)、signal driven I/O(SIGIO)以及asynchronous I/O(the POSIX aio_functions)。

在Windows系统上IO模型也是有5种:select、WSAAsyncSelect、WSAEventSelect、Overlapped I/O 事件通知以及IOCP。

同步阻塞 I/O

阻塞发生时,调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态,因此从处理的角度来看,这是非常有效的

例如:调用recv()/recvfrom()函数时,发生在内核中等待数据和复制数据的过程。

同步非阻塞 I/O

同步阻塞 I/O 的一种效率稍低的变种是同步非阻塞 I/O。在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操作不会立即完成,read 操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN 或 EWOULDBLOCK)

异步阻塞 I/O

——这个模型讲的是select方式,而这个方式在Linux中被认定为同步模型,具体见下文多路复用 I/O

另外一个阻塞解决方案是带有阻塞通知的非阻塞 I/O。在这种模型中,配置的是非阻塞 I/O,然后使用阻塞 select 系统调用来确定一个 I/O 描述符何时有操作。使 select 调用非常有趣的是它可以用来为多个描述符提供通知,而不仅仅为一个描述符提供通知。对于每个提示符来说,我们可以请求这个描述符可以写数据、有读数据可用以及是否发生错误的通知。

select 调用的主要问题是它的效率不是非常高。尽管这是异步通知使用的一种方便模型,但是对于高性能的 I/O 操作来说不建议使用。

由于 Unix 并没有定义所谓“异步阻塞”模型,而该模型所说的实际上是 I/O复用模型,我们可以认为:并不存在“异步阻塞”——Linux中都异步了,还阻塞个啥?同样的,如果实现异步了,也就不用再强调“非阻塞”了。所谓阻塞/非阻塞,都是针对同步操作而言的。

异步非阻塞 I/O(AIO)

最后,异步非阻塞 I/O 模型是一种处理与 I/O 重叠进行的模型。read 请求会立即返回,说明 read 请求已经成功发起了。在后台完成读操作时,应用程序然后会执行其他处理操作。当 read 的响应到达时,就会产生一个信号执行一个基于线程的回调函数来完成这次 I/O 处理过程。

对于异步操作,其回调机制可以看成是两个步骤:

  • 调用IO操作,并立即返回;
  • 调用Kernel对IO操作的结果(真实返回值)的查询(指定回调函数),也就是获得类似同步操作返回值的内容,然后根据返回值执行后续动作(也就是回调函数或信号处理函数的执行操作);

所以在客户端也可以通过两个函数执行异步操作——一个执行IO调用,另一个指定回调函数(或是指定Signal的处理函数),这与一个函数(携带了回调函数做实参)的方式其实没什么区别。


多路 I/O 就绪通知(I/O复用)

在 read / recv 执行前,先调用 select 或 poll / epoll,多路复用的过程也会使进程阻塞,但是和阻塞I/O所不同的的,这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。

在 select 与 recv 两个步骤之间的操作是同步进行的,而每个过程也都会阻塞进程,故而多路复用模型属于“同步阻塞模型”的一种特殊实现。

信号驱动式 I/O

我们允许套接口注册信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。

单独看通知过程,是异步操作,但执行IO的事件必须在通知事件完成后才能进行,所以总体来看,Linux将这个过程认定为同步模型。至于是否阻塞,通知阶段由于是异步过程,无所谓阻塞;而执行IO则是标准的阻塞操作(只是此时IO资源已经准备完成,其实也就无所谓阻塞了)。

但个人观点,信号驱动可以被称为“异步模型”,尽管在模型上,客户端得到通知后,才能处理IO操作,但这实际上与回调机制没什么差别——只是选择由客户端还是被调函数执行了IO调用的区别罢了。

小结

AIO / BIO / NIO ...

关于这几种io的对比,请参考博客《啊诶推送

网络IO模型《网络IO模型

关于select与poll的对比介绍《Linux五种IO模型

思考

实际上,同步与异步是针对应用程序与内核的交互而言的。

同步有阻塞和非阻塞之分,异步没有——它一定是非阻塞的。

阻塞、非阻塞、多路IO复用,都是同步IO,异步必定是非阻塞的,所以不存在异步阻塞和异步非阻塞的说法。真正的异步IO需要CPU的深度参与。

实际上同步与异步是针对应用程序与内核的交互而言的。

原文地址:https://www.cnblogs.com/brt3/p/9821089.html