一次搞清五种 I/O 模型(生动形象版)

一.基础概念

1. 同步 or 异步

同步和异步描述的是用户线程与内核的交互方式

  • 同步
    是指用户线程发起 I/O 请求后,需要等待或者轮询内核 I/O 操作完成后才能继续执行;

  • 异步
    是指用户线程发起 I/O 请求后仍继续执行,当内核 I/O 操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

2. 阻塞 or 非阻塞

阻塞和非阻塞描述的是用户线程调用内核 I/O 操作的方式

  • 阻塞
    是指 I/O 操作需要彻底完成后才返回到用户空间;

  • 非阻塞
    是指 I/O 操作被调用后立即返回给用户一个状态值,无需等到 I/O 操作彻底完成。

3. 四种组合模式

以上四种模式自由组合就分成了同步阻塞,同步非阻塞,异步阻塞,异步非阻塞,看着就头晕,翻译成人话就是这样一个去买炸鸡的场景:

同步阻塞

假如你去买炸鸡吃,点完餐了你就堵那儿不走(阻塞),不停的问,我的鸡好了么,我的鸡好了么,一直问到服务小姐姐被烦到暴揍你一顿把鸡给你为止(同步);
在这里插入图片描述

同步非阻塞

假如还是你去买炸鸡吃,点完餐了你怕被后面的人打死就去旁边溜达了(非阻塞),但是还是不停的问,我的鸡好了么,我的鸡好了么,直到你拿到鸡为止(同步);
在这里插入图片描述

异步阻塞

假如你又去买炸鸡吃,点完餐了还是堵那儿不走(阻塞),但是这次你怕被服务小姐姐打死,就原地乖乖等着,直到小姐姐通知你鸡好了(异步);
在这里插入图片描述

异步非阻塞

假如你去买炸鸡吃,点完餐你去小旮旯蹲着画画去了(非阻塞),因为被打的次数太多你也不敢再不停的问了,直到小姐姐通知你鸡好了(异步);
在这里插入图片描述

二.Unix I/O 模型

Unix 下共有五种 I/O 模型:

  • 阻塞 I/O
  • 非阻塞 I/O
  • I/O 多路复用
  • 信号驱动 I/O
  • 异步 I/O

1. 阻塞 IO 模型

最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。

当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就等待数据就绪,而用户线程就会处于阻塞状态,并且交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除 block 状态。

说人话版本

你说我要炸鸡(用户线程发出IO请求),服务小姐姐去看看炸鸡好了没(内核查看数据是否就绪),没有的话就等着炸鸡炸好(等待数据就绪),你就站那儿不敢再问悄悄等着你的鸡(用户线程会处于阻塞状态),服务小姐姐就去忙别的了(交出CPU)。当炸鸡好了(数据就绪),小姐姐就把鸡给你(内核拷贝数据到用户线程),并且耐心地告诉你鸡好了(返回结果给用户线程),你就拿着鸡心满意足地回家了(用户线程解除 block 状态)。
在这里插入图片描述


2. 非阻塞 I/O 模型

当用户线程发起一个read操作后,无需等待马上就能得到结果。如果结果是数据未准备好,它就会再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么内核马上就将数据拷贝到用户线程,然后返回结果。

非阻塞IO情况下,用户线程需要不断地询问内核数据是否就绪,也就是说,非阻塞IO不会交出CPU,会一直占用。

说人话版本

你去买炸鸡,问服务员小姐姐:我的鸡好了么(用户线程发起一个read操作),服务员小姐姐立刻告诉你:没有(无需等待马上就能得到结果),你又问,炸鸡好了么(再次发送read操作),服务员小姐姐强忍住打你的冲动告诉你:还是没有!一旦炸鸡好了(内核中的数据准备好了),你又不要命的问,鸡好了么(又再次收到了用户线程的请求),小姐姐烦燥的把炸鸡丢给你(内核马上就将数据拷贝到用户线程),告诉你,好了好了赶紧滚!(返回结果)

这种情况下,你不断的问小姐姐(用户线程需要不断地询问内核数据是否就绪),人家就不能去干别的了(会一直占用CPU)。
在这里插入图片描述


3. I/O 多路复用模型

在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以大大减少了资源占用。

多路复用 IO 为何比非阻塞 IO 模型的效率高?是因为在非阻塞 IO 中,不断地询问 socket 状态是通过用户线程去进行的,而在多路复用 IO 中,轮询每个 socket 状态是内核在进行的,这个效率要比用户线程要高的多。

说人话版本

假如今天,你和你的三大姑八大姨七舅老爷都要吃炸鸡,你给他们一个人点了一份,不断的问,我和我家里人的鸡做好了么?(会有一个线程不断去轮询多个socket的状态,即一个线程管理多个socket),服务员小姐姐开始以为你的亲戚们都要一起点餐,吓得瓜子都掉了,后来发现只需要应付你一人,脆弱的心感到了些许安慰,默默放下了准备打你的拳头(系统不需要建立新的进程或者线程,也不必维护这些线程和进程)。
在这里插入图片描述

一般情况下,你的七大姑八大姨们需要都去排队点餐,不断的去问,我的鸡好了么,我的鸡好了么,服务员小姐姐很容易疯掉(非阻塞IO中,需要用户线程不断地询问 socket 状态);而当前情况下,只需要服务员小姐姐去各个炸鸡窗口问问炸鸡们好了没,然后通知你就ok了(轮询每个 socket 状态是内核在进行的)。


4. 信号驱动 I/O 模型

在信号驱动IO模型中,当用户线程发起一个IO请求操作,会注册一个信号处理的回调函数,然后用户线程会继续执行,当内核数据就绪时,会发送一个SIGIO信号给用户线程,并回调我们注册的信号回调函数,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。

说人话版本

你去炸鸡店说给我来份孜然爆浆大鸡排(用户线程发起一个IO请求操作),服务员小姐姐给了你一个会叫唤的号码牌让你爱干啥干啥去(注册一个信号处理的回调函数),你拿着号码牌就去隔壁健身房看帅哥了(用户线程会继续执行)。当你的炸鸡好了,号码牌会发出声音(当内核数据就绪时,会发送一个SIGIO信号给用户线程),你听到后就回到炸鸡店(用户线程接收到信号之后),问小姐姐:我是号码牌110的用户,我要我的孜然爆浆大鸡排(进行IO请求操作),你取回你的鸡排,蹦蹦哒哒回家了。


5. 异步 IO 模型

在异步IO模型中,当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态,通知和回调通知调用者输入输出操作。

IO操作都不会阻塞用户线程,都是由内核自动完成,然后发送一个信号告知用户线程操作已完成,用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。

说人话版本

你去点炸鸡,说我要黑椒炸鸡柳,服务员小姐姐心情不好不想理你,留了你的手机号码就让你滚。(一个异步过程调用发出后,调用者不能立刻得到结果)当你的鸡炸好后,小姐姐给你发条短信说:你的鸡柳好了,滚回来拿!(实际处理这个调用的部件在完成后,通过状态,通知和回调通知调用者输入输出操作)

买炸鸡的过程你的存在感微弱,都是小姐姐在主动(IO操作都不会阻塞用户线程,都是由内核自动完成)。和前一个场景(信号驱动模型)最大的区别是,之前你需要和小姐姐说,我点了鸡排,请给我鸡排(需要用户线程调用IO函数进行实际的读写操作);而在当前场景中(异步IO模型),不用你再哔哔说你点的是啥,应该已经炸好了是不是之类(不需要再在用户线程中调用iO函数进行实际的读写操作),小姐姐直接就知道你要的是鸡柳,你只要回来拿就可以了。


经过多次购买,你最终被该炸鸡店拉黑了,真是个圆满的结局啊。

在这里插入图片描述


欢迎关注我的公众号,用讲故事的方式学技术。

这里有脑洞大开的奇葩故事,也有温暖文艺的心灵感悟。

技术知识,也可以很有趣。
在这里插入图片描述

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盛夏温暖流年

可以赏个鸡腿吃嘛~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值