zoukankan      html  css  js  c++  java
  • 玩转BIO,NIO,AIO模型, 阻塞 同步 与IO多路复用

    1.啥是IO?

    2.讲明白同步和阻塞俩概念

    3.啥是IO多路复用,操作系统层的演进

    4.对比BIO,NIO,AIO

    四步玩转IO模型

    1.啥是IO

    计算机核心三大功能:   Input   计算   OutPut

    IO重要吧 没有IO计算机就是玩具 

    那IO又可分为几种 常用的,磁盘IO  网络IO  

    闭眼睛想 如果IO不给力,计算机运算能力再牛逼 会不会影响整体效率的,非常影响

    从底层磁盘看IO:  啥是磁盘IO

    2. 同步与阻塞

    同步与异步,指的是API,接口,服务调用等层面的概念。

    同步指的是一步一步来,上一步执行完 返回结果 才能执行下一步, 异步指的是 不需要等上一步执行完返回结果,就能执行下一步。

    举个简单的例子就是 java线程池, 把具体的任务丢到线程池里就不管了,主线程可以继续干别的事了,不需要阻塞在那等任务执行完,线程池主要的目的也是为了完成异步。

    通常异步的执行可以通过回调接口来获取执行结果,如FutureTask,可以获取到异步执行的情况

    阻塞与非阻塞,指的是底层操作系统IO层面的概念。

    阻塞指的是 进程调用操作系统执行IO,IO执行完成之前,进程会一直阻塞在那等待,直到IO完成获取到返回结果

    非阻塞指的是 进程调用操作系统执行IO,IO没有完成会直接返回失败结果,进程循环去调用直到IO完成,或者IO完成后操作系统回调接口来通知进程拿数据。总而言之 进程不需要在那一动不动的阻塞住等IO  这就是非阻塞。

    现在区别开同步和阻塞,一个是应用接口调用层面,一个是底层操作系统IO层面,两者的思想很相近

    3.啥是IO多路复用,操作系统层的演进

    模拟一个场景:

    我们这台机器作为服务端,另一台机器作为客户端

    客户端请求创建一次网络连接,随后通过网络传输数据

    此时在我们机器读取数据过程

    我们创建一个线程,用来监听新创建的Socket,读取发来的数据

    阻塞式IO:

    线程调用read函数,如果没有数据发来 就一直阻塞,

    数据从网卡传到内核态,内核缓冲区复制到用户缓冲区,传输过程中线程保持阻塞状态, 直到拿到数据返回结果

    非阻塞IO:

    不用等数据传输完,即可返回给线程一个结果(-1 或失败), 这样可以放开线程去干别的事,

    需要用户线程循环调用 read,直到返回值不为 -1,再开始处理业务。

    但是这样有个明显的缺点    线程想拿到数据 就得不断一遍一遍调用,一遍一遍询问数据传好了没有, 也会有不小的开销,

    多线程并发执行的情况下 每一个Socke监听都会耗着一个线程,消耗线程资源

    IO多路复用 select:

    不再为每一次Socket监听创建一个线程了,而是一个线程同时服务多个Socket套接字

    在用户态,每创建一个套接字 添加一个文件描述符fd 到数组里

    用户态将fd数组拷贝到内核态,执行select函数

    select函数 让内核去遍历数组中的每个元素,执行非阻塞的判断,判断是否Socket准备好数据了

    最后select返回准备好的fd个数,通知用户态

    用户态执行read函数去读数据

    优点:

    由内核态做遍历,有效的减少了每次遍历判断要经过用户态 内核态之间切换 !!

    问题:

    1.select返回结果只是fd个数,具体哪个fd准备好了还要用户态自己遍历 !!

    2.select需要拷贝fd数组到内核态,高并发场景下消耗较大

    3.select在遍历的过程中也是阻塞的,用户态需要阻塞的等待内核态返回结果

    IO多路复用 poll:

    poll只是在select基础上增强,它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。

    IO多路复用 epoll:

    1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。

    2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。

    3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

    总结一下 整个过程,

    IO多路复用解决的问题是,我的服务端同时和十个客户端建立连接 等着客户端发数据,那我怎么随时随地直到哪个客户端发数据了 我好读数据呢?

    原始办法就是每一个连接 创建一个线程阻塞的等待客户端消息,这显然不够友好

    IO多路复用就通过一个线程同时监听十个客户端,轮训着客户端套接字 哪个好了通知我读哪个不就好了。

    如果轮训遍历是在用户态完成的,每一次判断数据准备好都要用户态切内核态,内核态切用户态 这样是不是效率很低?

    索性把是个客户端代表的文件描述符传到内核态,让内核态去遍历,遍历出结果再告诉用户态就完了呗。

    最后的read读数据 还是要用户态去读。只不过是有确定的去读

    4.对比BIO,NIO与AIO

    BIO: 也称同步阻塞IO,顾名思义 应用调用层是同步的,操作系统层是阻塞的。 可以参考java InputStream流,当read磁盘数据时,read()方法会阻塞在那,而底层也会阻塞在那等待IO完成 

    NIO: 也称同步非阻塞IO,应用调用层是同步的,操作系统层是非阻塞的。 如果基于NIO进行网络通信,采取的就是多路复用的IO模型,这个多路复用IO模型针对的是网络通信中的IO场景来说的。

    AIO: 也成异步非阻塞IO,也叫做NIO2.0 异步IO模型,应用调用层是异步的,操作系统层是非阻塞的。应用层调用不管IO是否成功都会直接返回,不会阻塞住线程。你需要提供一个回调函数给AIO接口,一旦底层系统内核完成了具体的IO请求,比如网络读写之类的,就会回调你提供的回调函数。

  • 相关阅读:
    多态
    接口和抽象类
    反射
    C++ 模板和 C# 泛型的区别
    基础类库中的泛型
    运行时中的泛型
    泛型代码中的 default 关键字
    泛型委托
    泛型方法
    泛型接口
  • 原文地址:https://www.cnblogs.com/ttaall/p/15123298.html
Copyright © 2011-2022 走看看