通过上篇文章(【死磕NIO】— 阻塞、非阻塞、同步、异步,傻傻分不清楚),我想你应该能够区分了什么是阻塞、非阻塞、异步、非异步了,这篇文章我们来彻底弄清楚什么是阻塞IO,非阻塞IO,IO复用,信号驱动IO,异步IO。

要想彻底弄清楚这五种IO模型,我们需要先弄清楚几个基本概念。

基本概念

什么是IO

什么是IO?维基百科上面是这样解释的:

I/O(英语:Input/Output),即输入/输出,通常指数据在存储器(内部和外部)或其他周边设备之间的输入和输出,是信息处理系统(例如计算机)与外部世界(可能是人类或另一信息处理系统)之间的通信。输入是系统接收的信号或数据,输出则是从其发送的信号或数据。

这是IO一个完整的定义,不是特别好理解,要厘清IO这个概念,我们需要从如下两个视角来理解它。

计算机视角理解IO

冯•诺伊曼计算机的基本思想中有提到计算机硬件组成应为五大部分:控制器,运算器,存储器,输入和输出。其中输入是指将数据输入到计算机的设备,输出是指从计算机中获取数据的设备。对于计算机而言,任何涉及到计算机核心(CPU和内存)与其他设备间的数据转移的过程就是IO。

IO 对于计算机而言,有两层意思:

  • IO 设备。比如我们最常见的打印机、鼠标、键盘

  • 对IO设备的数据读写

程序视角理解IO

程序视角我们关注的则是应用程序本身。我们知道应用程序只有加载到内存中作为一个进程才能运行,它需要时刻与计算机进行数据交换,比如读写磁盘、远程调用、访问内存等等,但是操作系统为了能够正常平稳地运行下去,它是不会运行应用程序随意访问计算机硬件部分,如内存、硬盘、网卡,应用程序必须通过操作系统提供的API来访问,以达到安全的访问控制。所以应用程序如果要访问内核管理的IO,则必须通过有操作系统提供的API来间接访问。所以 IO对应应用程序而言,强调的则是 通过向内核发起系统调用完成对I/O的间接访问

所以,换句话说应用程序发起一次IO访问是分为两个阶段的:

  1. IO 调用阶段:应用程序向内核发起系统调用。

  2. IO执行阶段:内核执行IO操作并返回。

    1. 数据准备阶段:内核等待IO设备准备好数据
    2. 数据拷贝阶段:将数据从内核缓冲区拷贝到用户空间缓冲区

用户空间&内核空间

操作系统是利用CPU 指令来计算和控制计算机系统的,有些指令很温和,我们操作它不会对操作系统产生什么危害,而有些指令则非常危险,如果使用不当则会导致系统崩溃,如果操作系统允许所有的应用程序能够直接访问这些很危险的指令,这会让计算机大大增加崩溃的概率。所以操作系统为了更加地保护自己,则将这些危险的指令保护起来,不允许应用程序直接访问。

现代操作系统都是采用虚拟存储器,操作系统为了保护危险指令被应用程序直接访问,则将虚拟空间划分为内核空间和用户空间。

  • 内核空间则是操作系统的核心,它提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。

  • 用户空间,非内核应用程序则运行在用户空间。用户空间中的代码运行在较低的特权级别上,只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。

进行空间划分后,用户空间通过操作系统提供的API间接访问操作系统的内核,提高了操作系统的稳定性和可用性。

想要详细了解Linux 内核空间和用户空间,则可以关注如下两篇文章:

用户态和内核态进程切换

  • 内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘,、网卡,CPU也可以将自己从一个程序切换到另一个程序。

  • 用户态: 只能受限的访问内存, 且不允许访问外围设备。占用CPU的能力被剥夺, CPU资源可以被其他程序获取。

我们知道CPU为了保护操作系统,将空间划分为内核空间和用户空间,进程既可以在内核空间运行,也可以在用户空间运行。当进程运行在内核空间时,它就处在内核态,当进程运行在用户空间时,他就是用户态。开始所有应用程序都是运行在用户空间的,这个时候它是用户态,但是它想做一些只有内核空间才能做的事情,如读取IO,这个时候进程需要通过系统调用来访问内核空间,进程则需要从用户态转变为内核态。

用户态和内核态之间的切换开销有点儿大,那它开销在哪里呢?有如下几点:

  • 保留用户态现场(上下文、寄存器、用户栈等)

  • 复制用户态参数,用户栈切到内核栈,进入内核态

  • 额外的检查(因为内核代码对用户不信任)

  • 执行内核态代码

  • 复制内核态代码执行结果,回到用户态

  • 恢复用户态现场(上下文、寄存器、用户栈等)

所以,频繁的IO操作会频繁的造成用户态 —> 内核态 —> 用户态的切换,这严重会影响系统性能。后面小编会介绍IO的一些优化,重点就是减少切换。

好了,就到这里了,想要详细了解用户态和内核态,可以关注如下两篇文章:

五种IO模型

《UNIX网络编程》说得很清楚,5种IO模型分别是 阻塞IO模型非阻塞IO模型IO复用模型信号驱动IO模型异步IO模型。前4种为同步IO操作,只有异步IO模型是异步IO操作。

这里小编问个问题,为什么前四种是同步,而只有异步IO模型才是异步呢?

阻塞IO模型

阻塞IO模型是最常见最简单的IO模型,图如下:

应用程序发起一个系统调用(recvform),这个时候应用程序会一直阻塞下去,直到内核把数据准备好,并将其从内核复制到用户空间,复制完成后返回成功提示,这个时候应用程序才会继续处理数据。

所以,阻塞IO模型在IO两个阶段都会阻塞

  • 优点

    • 模型简单,实现难度低

    • 适用于并发量较小的应用开发

  • 缺点

    • 整个过程都阻塞,进程一直挂起,程序性能较为低,不适用并发大的应用

场景

某天,你跟你女朋友(假如你有女朋友)去饭店吃饭,点完餐后,你就做坐那里一直等菜做好后,吃饱喝足才离开。这期间你和你女朋友由于担心不知道菜什么时候才能做好,所以这个期间你们就只能一直在座位上面等着,什么时候也不能干。

非阻塞 IO模型

非阻塞IO模型图例如下:

应用程序发起recvform系统调用,如果数据报没有准备会则会立即返回一个EWOULDBLOCK错误码,进程并不需要进行等待。进程收到该错误后,判断内核数据还没有准备好,它还可以继续发送 recvform,如果数据报已经准备好了,待数据从内核拷贝到用户空间返回成功指示后,进程则可以处理数据报了,

所以, 非阻塞IO模型需要应用进程不断地主动询问内核数据是否已准备好了

  • 优点

    • 模型简单,实现难度低

    • 与阻塞IO模型对比,它在等待数据报的过程中,进程并没有阻塞,它可以做其他的事情

  • 缺点

    • 轮询发送 recvform ,消耗CPU 资源

    • 与阻塞IO模型一样,它也不适用于并发量大的应用程序

场景

一个星期后,你跟你女朋友还是去那家餐厅吃饭,点完菜后,你女朋友吸取上次教训,知道要在这里干等,所以还不如去逛逛,买点香水口红啥的。但是呢,由于你们担心会错过上菜,所以你们就每隔一段时间就来问下服务员,你们的菜准备好了没有,来来回回好多回,若干次后,终于问到菜已经准备好了,然后你们就开心的吃起来。

IO复用模型

基于非阻塞IO模型,我们知道,它需要进程不断地轮询发起recvform系统调用,在整个过程中,轮询会占据很大一部分过程,而且不断轮询是很消耗CPU的。而且我们又不是只有一个进程在这里发起recvform系统低调用,有可能是几万几十万个。

我们可以想象这样一个场景,你是喜茶服务员,每个人点好奶茶后,都会过来问你他的奶茶好了没有,三五个人你顶得住,那几十上百个人呢?而且他们又不是只问一次,而是每隔几分钟就来问你一次,就问你烦不烦?我估计你都会怀疑人生了。那有办法解决没有呢?这么多人来问你受不了,一个人问不就可以解决了?奶茶做好了,由他来通知你们不就可以了?

IO复用模型采用的就是这种方式,不需要所有进程轮询来发起recvform来查询数据是否已经准备好了,而是有人帮忙来询问,这个帮忙的人就是select

IO复用模型图例如下:

多个进程的IO注册到一个复用器(select)上,然后用一个进程监听该 select,select 会监听所有注册进来的IO。如果内核的数据报没有准备好,select 调用进程会被阻塞,而当任一IO在内核缓冲区中有数据,select调用就会返回可读条件,然后进程再进行recvform系统调用,内核将数据拷贝到用户空间,注意这个过程是阻塞的。

  • 优点

    • 一个进行负责状态监听,性能较好。

    • 适用于高并发应用程序

  • 缺点

    • 模型复杂,实现、开发难度较大

场景

还是那家餐厅,开始的时候,大家都是在那里等,服务员只需要等菜做好,端上来就可以了,某天有些小伙伴发现你跟你女朋友竟然利用等菜的空闲时间去逛街(虽然累,但好歹也买了几件东西对吧),然后他们也采用了这种方式,这个时候服务员就受不了了,你们隔一段时间就来问,隔一段时间就来问,烦都烦死了,于是他想了一个办法,说,你们派一个人来问就可以了,我这边做了由他来告诉你们菜是否已经做了好。

后面小编会有专门的一篇文章来分析IO多路复用,敬请期待!!

信号驱动IO模型

IO 复用模型在第一个阶段和第二个阶段其实都有阻塞,第一个阶段阻塞于 select 调用,第二个阶段阻塞于数据复制,那有没有办法在第一个阶段或者第二个阶段不阻塞,进一步提升性能呢?信号驱动IO模型。图例如下:

进程发起一个IO操作,会向内核注册一个信号处理程序,然后 立即返回不阻塞,当内核将数据报准备好后会发送一个信号给进程,这时候进程便可以在信号处理程序中调用IO处理数据报。它与IO复用模型的主要区别是等待数据阶段无阻塞。

  • 优点

    • 采用回调机制,等待数据阶段无阻塞

    • 适用于高并发应用程序

  • 缺点

    • 模型较为复杂,实现起来有点儿困难

场景

有人帮你问,其实也不是那么好,因为你还是要等他来告诉你,而且他是只要你们当中有一个人的菜做好了就告诉你们所有人。于是,你们又想了一种方案,我点完菜后,我告诉服务员,我留我的微信在你这里,菜做好后,你告诉我就可以了。这样你女票就可以利用这个空余时间逛更久了。

异步IO模型

信号驱动IO模型,进一步优化了IO操作流程,经过了三轮优化,它终于不用在数据等待阶段阻塞了,但是在数据复制节点依然是阻塞的,所以如果我们需要进一步优化的话,只需要把第二个阶段也进一步优化为异步,我们就大功告成了,也就变成了真正的异步IO了。

当进程发送一个IO操作,进程会立刻返回(不阻塞),但是也不能发挥结果,内核会把整个IO数据报准备好后,再通知进程,进程再处理数据报。

场景

经过了前面四次的

  • 优点

    • 整个过程都不阻塞,一步到位

    • 非常使用高并发应用

  • 缺点

    • 需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性

    • 模型复杂,实现、开发难度较大

场景

虽然留电话的方式不错,你只需要留一个微信就可以了,瞬间解放了,后面你又发现了一个问题,你到了餐厅后,还不能立刻吃饭,因为他们还要上菜,这个过程你还是要等,如果你到店后立刻就可以吃难道不是更爽么?所以你服务员沟通说,你做好菜后,直接上,完成后再微信通知你,你到店后就直接吃了。这样你是不是更加爽了?

总结

五种IO模型,层层递进,一个比一个性能高,当然模型的复杂度也一个比一个复杂。最后用一张图来总结下

通过这张图,我想你应该可以回答文章前面的那个问题了。

参考

【死磕NIO】— 阻塞IO,非阻塞IO,IO复用,信号驱动IO,异步IO,这你真的分的清楚吗?的更多相关文章

  1. IO模型浅析-阻塞、非阻塞、IO复用、信号驱动、异步IO、同步IO

    最近看到OVS用户态的代码,在接收内核态信息的时候,使用了Epoll多路复用机制,对其十分不解,于是从网上找了一些资料,学习了一下<UNIX网络变成卷1:套接字联网API>这本书对应的章节 ...

  2. 🍛 餐厅吃饭版理解 IO 模型:阻塞 / 非阻塞 / IO 复用 / 信号驱动 / 异步

    IO 概念 一个基本的 IO,它会涉及到两个系统对象,一个是调用这个 IO 的进程对象,另一个就是系统内核 (kernel).当一个 read 操作发生时,它会经历两个阶段: 通过 read 系统调用 ...

  3. 【死磕NIO】— 阻塞、非阻塞、同步、异步,傻傻分不清楚

    万事从最基本的开始. 要想完全掌握 NIO,并不是掌握上面文章([死磕NIO]- NIO基础详解)中的三大组件就可以了,我们还需要掌握一些基本概念,如什么是 IO,5 种IO模型的区别,什么是阻塞&a ...

  4. IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别参考

    参考https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral IO复用,AI ...

  5. IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别(百度)

    如果面试问到IO操作,这篇文章提到的问题,基本是必问,百度的面试官问我三个问题 (1)什么是NIO(Non-blocked IO),AIO,BIO (2) java IO 与 NIO(New IO)的 ...

  6. (转)IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别

    本文来自:https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral,非常感谢 ...

  7. IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别

    一.什么是socket?什么是I/O操作? 我们都知道unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO.管道.终端,对我们来说,一切都是 ...

  8. Linux NIO 系列(03) 非阻塞式 IO

    目录 一.非阻塞式 IO 附:非阻塞式 IO 编程 Linux NIO 系列(03) 非阻塞式 IO Netty 系列目录(https://www.cnblogs.com/binarylei/p/10 ...

  9. AIO,BIO,NIO,IO复用,同步,异步,阻塞和非阻塞

    (1)什么是NIO(Non-blocked IO),AIO,BIO (2) 区别 (3)select 与 epoll,poll区别 1.什么是socket?什么是I/O操作? 什么是socket? 实 ...

随机推荐

  1. SoutceTree用户名或者密码输入错误解决方案

    soutceTree在拉取代码时候需要输入账户名或者密码,如果一时输入错了,可以这样修改: 1.找到这个目录:C:\Users\Administrator\AppData\Local\Atlassia ...

  2. URL的加密解密方法

    package day11.about_url_encoder; import java.io.UnsupportedEncodingException; import java.net.URLDec ...

  3. 初识apache DBCP连接池

    连接案例: 首先:我们使用的是mysql数据库,所以要有一个mysql和java的JDBCjar包: 然后是DBCP中的两个jar包,DBCP使用的话,需要两个包: dbcp.jar和pool.jar ...

  4. 并发编程之:JMM

    并发编程之:JMM 大家好,我是小黑,一个在互联网苟且偷生的农民工. 上一期给大家分享了关于Java中线程相关的一些基础知识.在关于线程终止的例子中,第一个方法讲到要想终止一个线程,可以使用标志位的方 ...

  5. mysql switch语句

    SELECT CASE the_order_status WHEN 4 THEN '待收货' WHEN 5 THEN '已收货' ELSE '其他' END AS statuss ,order_id ...

  6. windows 10 + tensorflow-gpu 环境搭建

    安装过程可基本按照ubuntu装法,参考https://www.cnblogs.com/xbit/p/9768238.html 其中: conda配置文件:C:\Users\Administrator ...

  7. idea导出jar包及坑

    导出基本步骤 1.打开项目结构,在artifact新建一个jar 2.然后填写主类和依赖 3.这里的坑: 4.查看 5.点击编译输出 6.得到jar包

  8. You have mail in /var/mail/xxx

    因为配置 DDNS, 我添加了个 crontab 定时任务,每隔 1 分钟执行一段 python 脚本 然后就发现 terminal 经常提示 'You have mail in /var/mail/ ...

  9. Appium问题解决方案(9)- Original error: Failed to launch Appium Settings app: Condition unmet after 5090 ms

    背景 执行代码报错 解决方法 该问题并不常见,主要是手机操作系统的问题 程序无法无法自动打开appiumsettings,那么我们可以手动打开appiumsettings服务(程序没有界面,会出现打开 ...

  10. 清除router路由后面的参数

    清除router参数: 1.this.$router.push({ query: {}}) 2.var path = this.$route.path; //获取路由路径    this.$route ...