我们在学习操作系统等计算机相关知识的时候,经常会见到阻塞、非阻塞、同步、异步这几个关键词。尤其是通过纸面资料学习,而不是自己动手实践的过程中更容易混淆这几个知识点。
最经典的错误观点:阻塞就是同步,非阻塞就是异步!
前面简单介绍过I/O多路复用(I/O多路转接)-CSDN博客、多线程实现并发服务-CSDN博客、多进程实现并发服务器-CSDN博客。这三者用涉及到的都是同步IO。
一次IO,两个阶段
我们把一次获取数据的过程,简单的划分为两个阶段:数据就绪(等待数据到达)和数据读写(处理数据)这两个过程。
先直接结论以通俗易懂的方式告诉你:
阻塞、非阻塞发生在数据就绪阶段,同步、异步发生在数据读写阶段。这句话咋一看,怪怪的,不够严谨,但接着往下看就能深入理解了。
引入一个生活中的例子,一只小猪肚子饿了,他现在需要到饭店吃饭。
第一种情况(阻塞态):
- 猪猪肚子饿了,走进饭店,目的是填饱肚子。
- 猪猪:老板,我要一碗猪脚饭。
- 老板:好的,现在做,要稍等十分钟做好。
- (猪猪乖乖坐在位置上等了10分钟)
- 老板:饭做好啦,拿去吃吧。
- 猪猪吭哧吭哧地吃了5分钟,填饱了肚子。
在这种情况中,老板在做饭的时候,猪猪什么事也没做,阻塞等待老板做饭。也就是对应着,数据就绪阶段阻塞了,没有处理其他事件。再看看非阻塞状态是怎样的。
第二种情况(非阻塞态):
- 猪猪肚子饿了,走进饭店,目的是填饱肚子。
- 猪猪:老板,我要一碗猪脚饭。
- 老板:好的,现在做,要稍等一会。
- 猪猪:好的,那我先去隔壁买杯奶茶吧。
- (猪猪去买奶茶(做了别的事),每隔1分钟打电话问老板饭做好了没)
- 老板:饭做好啦,回来吃吧。
- 猪猪回到饭店吭哧吭哧地吃了5分钟,填饱了肚子。
以上两种情况很好地解释了什么是阻塞和非阻塞。总结一下就是在数据就绪阶段,如果进程或者线程能够去处理其他事情,而不是单纯地原地等待数据的到来那就是非阻塞,反之就是阻塞。
再来分析下同步和异步,前面介绍的以上两种情况都是同步,为什么呢?先告诉答案。
因为以上两种情况,对于猪猪饿了,目的是填饱肚子,在老板做好饭,把饭给到猪猪后,在猪猪填饱肚子地这个过程中,是由猪猪自己进食的,也就是猪猪还是花了自己的时间在进食上。如果老板能够在做好饭后直接一整碗直接塞进猪肚子里,再给猪猪一个信号:你要的饭我已经帮你塞进肚子里了,你自己慢慢消化吧。
第三种情况(异步):
- 猪猪肚子饿了,走进饭店,目的是填饱肚子。
- 猪猪:老板,我要一碗猪脚饭。
- 老板:好的,现在做,要稍等一会。
- 猪猪:好的,那我先打会游戏吧。
- 老板:饭做好啦,还在打游戏!
- (老板一个箭步,cua~,直接一整碗塞进猪猪肚子里)
- 老板:好了,你可以走了。
言归正传,做个总结
数据就绪:根据系统IO操作的就绪状态
- 阻塞
- 非阻塞
数据读写:根据应用程序和内核的交互方式
- 同步
- 异步
引用一句话:在处理 IO 的时候,阻塞和非阻塞都是同步 IO,只有使用了特殊的 API 才是异步 IO。
一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪” 和 “数据读写”,数据就绪阶段分为
阻塞和非阻塞,表现得结果就是,阻塞当前线程或是直接返回。
同步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是
由请求方A自己来完成的(不管是阻塞还是非阻塞);异步表示A向B请求调用一个网络IO接口时
(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以
处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。
我会在下一节更新Unix/Linux上的五种I/O模型相关内容,会结合本节内容更深入地进行探讨。