zoukankan      html  css  js  c++  java
  • 关于 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的深度参与。

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

  • 相关阅读:
    算法导论
    深度探索C++对象模型
    git 介绍及其使用总结
    前端跨域常见的几种方式
    前端面试angular 常问问题总结
    低版本浏览器支持HTML5标签的方法
    理解 angular 的路由功能
    Angular 新手容易碰到的坑
    Angular 新手容易碰到的坑
    一 Unicode和UTF-8的异同
  • 原文地址:https://www.cnblogs.com/brt3/p/9821089.html
Copyright © 2011-2022 走看看