五种Linux IO模型+同步异步,阻塞非阻塞,再也不怕头条面试官挂我了
背景
小K在面试中被头条面试官问了五种IO模型,只是含糊的回答了;头条面试官一脸不满意的回答,你回去花两小时认真看看吧,保证你有收获,最后,当然就是面试不通过了,所以在面试中,IO模型是非常常见的问题,我们需要理解IO模型的分类,特点,为什么有这种模型。
首先记住从245原则去讲解,2代表两类;第一类有4个名词,代表阻塞,非阻塞,同步,异步;第二类有5个名词,代表阻塞IO,非阻塞IO,IO多路复用,信号驱动IO,异步IO
第一类:同步异步 阻塞非阻塞
了解第一类之前,我们需要先了解一个IO操作做了什么事情,对于熟悉第一类的大佬,可以跳过第一类,去了解第二类IO模型
linux系统分为用户态和内核态,发起一个IO操作的过程分类两步:
1. 用户态的线程发起IO操作,那就要**等**内核准备好数据
2. 内核态准备好数据后,用户态唤醒,把**数据从内核拷贝**到用户的**线程缓冲区**中
1. 阻塞
容易记的关键词:线程停了;阻塞的意思就是用户的线程发起一个IO操作,线程就停止了,直到等待内核准备好数据,线程才能继续运行
2. 非阻塞
容易记的关键词:线程还能动;非阻塞的意思就是用户的线程发起一个IO操作,线程没有停止,还可以继续做其他事情,也就是说线程通知内核IO操作后,继续去做其他事情了
3. 同步
容易记的关键词:直到整个操作完成;同步的意思可以理解为做一件事件,必须等到了结果,才能继续做其他事情,那么对于IO操作,线程是需要做完了第一步等操作和第二部数据拷贝的操作,才能继续做其他的事情
4. 异步
容易记的关键词:不需要整个操作完成;异步的意思可以理解为做一件事情,通知开始做后,便可以做其他事情了,系统会帮我们做完剩下的事情,那么对于IO操作,线程只需要通知IO操作开始,就可以做其他操作了,最后内核会帮我们做完两件事情,等数据准备和数据拷贝
第二类:IO模型
现在便来介绍IO模型了,每个IO模型都有其特点,不断进化
1. 阻塞IO
看到阻塞IO,大家应该有印象了吧,那就是关键字:线程停了
应用的进程调用IO操作后,一直等到内核准备好数据,然后将数据从内核复制到用户的进程缓冲区,最后才能进行其他操作
场景描述:
小K需要跑一个程序,第一步需要编译,编译完成后,第二步再运行程序;阻塞IO场景就是小k编译程序后,一直盯着编译过程,等到编译完成后,就立刻运行编译好的程序;这样是不是不能做其他事情了,而且运行程序也无延迟
优点:
1. 不消耗CPU,发起IO后,进程就阻塞了,所以不消耗CPU
2. 能够及时返回数据,无延迟
缺点:
1. 进程不能进行其他操作,因为等待数据和准备好数据是需要时间的,如果能够不阻塞,那便可以进行其他的IO发起操作等
2. 性能较低
2. 非阻塞IO
针对阻塞IO,是不是可以优化一下,在阻塞的时候,去做一下其他事情,然后在看数据有没有准备好;所以整个操作可以看作,发起IO操作后,不进行阻塞,而是不断主动去轮询内核,看数据是不是准备好了,数据准备好后,就可以进行拷贝了
场景描述:
小K编译程序的时候,去写一下代码,然后回来看编译过程,重复这个过程,直到程序编译好,然后再运行程序
优点:
在等待任务完成的时候,还可以去做其他事情,提高CPU使用率
缺点:
1. 任务存在延迟,因为定时轮询,可能轮询不及时
2. 消耗CPU,因为定时去轮询时,进程不是阻塞态,是运行状态,所以是消耗CPU的,如果多个进程都定时轮询,CPU消耗是很大的
3. IO 多路复用
针对与非阻塞IO,多个进程都进行轮询的话,系统需要进程资源,也消耗CPU,所以支撑不了很多的IO操作,这样便出现了IO多路复用;我们可以把多个IO操作注册到IO复用的函数(select,poll,epoll)中,这样便可以由一个系统调用去监听多个IO,等到有一个或多个IO操作的数据就绪后,系统调用就会立刻去通知用户态,用户再进行该就绪IO的数据拷贝操作
场景描述:
小K一下需要编译10个程序,如果每个定时去看,得查看10次,很麻烦;聪明的小K便写了一个脚本,需要编译一个程序,就往脚本配置文件注册该编译程序的信息;脚本运行时,如果发现有编译完成的程序,立刻告诉小K,美滋滋,可以运行该程序了,运行完程序后,又盯着脚本看,如果再有编译好的程序,再运行。。。
优点:
1. 支持高数量的IO操作,适用于高并发的场景
2. 系统开销小,一个进程做多个IO操作
缺点:
1. 该模型还是同步模型,整个操作(等待数据和拷贝数据)都需要用户进程去完成
2. 在IO没有就绪的情况下,会阻塞进程
4. 信号驱动IO
在IO多路复用中,优点还是挺多的了,但是针对缺点1阻塞,和更好的针对网路协议,便有了信号驱动IO;信号驱动IO会安装好信号处理函数(可以理解为数据拷贝),进程发起信号驱动IO时,就立刻收到返回,便可以做其他事情了,等内核准备好数据时,便通知信号处理函数,信号处理函数便进行数据拷贝的操作
但是信号驱动IO适用于UDP协议,而不适用于TCP协议,因为UDP收到数据包时,才会产生信号,而TCP不同,三次握手,四次挥手,心跳检查等等,都会产生信号,这样信号程序是无法判断是否是数据包的(如有错误,请各位大佬提醒)
场景描述:
聪明的小K在编译脚本的基础上加了弹框功能弹框功能,如果有就绪情况,就立刻弹框,小K便可以知道编译完成了,去运行程序
优点:
1. 非阻塞模型,进程发起调用后,立刻返回,便可以进行其他操作了
缺点:
1. 在数据拷贝的时候,还是需要用户态去主动去操作
5. 异步非阻塞IO
这个就是真正强大的IO,用户发起IO操作后,就不用管了,去做其他事情,内核会准备好数据,并把数据拷贝到用户的进程中
场景描述
小K现在把编译脚本加上了运行脚本,只要发起脚本,就可以去写其他代码了,脚本会自动完成编译+运行操作
优点:
1. 非阻塞,发起IO操作后,还可以做其他事情
2. 异步,整个IO操作,只需要发起操作,而不需要进行等待数据完成和数据拷贝操作,这些内核都做好了
缺点:
1. 优点系统不支持