本文转自 https://www.jianshu.com/p/aed6067eeac9node
关于IO多路复用,网上有各类说法,最多见的是认为是异步阻塞的,好比这里的https://cloud.tencent.com/developer/article/1165632,我对这个观点不是很认同,这个更像是对IO多路复用模型,即各类框架如ACE中的reactor,nodejs的实现描述,而不是对系统层面IO多路复用的描述;react
还有聊聊Linux 五种IO模型中详细讲解了IO模型,写的很不错, https://www.jianshu.com/p/486b0965c296,做者将IO多路复用归为同步阻塞,理由是select时是阻塞的,我部分认同这个观点,,我比较承认git
https://github.com/CyC2018/CS-Notes/issues/194 这里的观点,即IO多路复用是同步的,阻塞或者不阻塞github
近来遇到了一些常见的概念,尤为是网络编程方面的概念,如:阻塞、非阻塞、异步I/O等等,对于这些概念本身也没有太清晰的认识,只是很模糊的概念,说了解吧也了解,可是要让本身准确的描述概念方面的具体细节,却说的不那么准确,这也是本身在这几个方面也没有细细考究过的缘由吧。通过看了些这几个概念的资料,发现同步、异步、阻塞、非阻塞的概念其实也并不难以理解,在此写下此文,欢迎拍砖,但愿多多交流。编程
首先来解释同步和异步的概念,这两个概念与消息的通知机制有关。也就是同步与异步主要是从消息通知机制角度来讲的。
网络
所谓同步就是一个任务的完成须要依赖另一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
。要么成功都成功,失败都失败,两个任务的状态能够保持一致。多线程
所谓异步是不须要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工做,依赖的任务也当即执行,只要本身完成了整个任务就算完成了
。至于被依赖的任务最终是否真正完成,依赖它的任务没法肯定,因此它是不可靠的任务序列
。框架
异步的概念和同步相对
。当一个同步调用发出后,调用者要一直等待返回消息(结果)通知后
,才能进行后续的执行;当一个异步过程调用发出后,调用者不能马上获得返回消息(结果)。实际处理这个调用的部件在完成后,经过状态、通知和回调来通知调用者
。异步
这里提到执行部件和调用者经过三种途径返回结果:状态、通知和回调
。使用哪种通知机制,依赖于执行部件的实现
,除非执行部件提供多种选择,不然不受调用者控制
。函数
若是执行部件用状态来通知,那么调用者就须要每隔必定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这实际上是一种很严重的错误);
若是是使用通知的方式,效率则很高,由于执行部件几乎不须要作额外的操做。至于回调函数,其实和通知没太多区别。
举个例子,好比我去银行办理业务,可能会有两种方式:
选择排队等候;
另种选择取一个小纸条上面有个人号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务了;
第一种:前者(排队等候)就是同步等待消息通知
,也就是我要一直在等待银行办理业务状况;
第二种:后者(等待别人通知)就是异步等待消息通知
。在异步消息处理中,等待消息通知者(在这个例子中就是等待办理业务的人)每每注册一个回调机制
,在所等待的事件被触发时由触发机制(在这里是柜台的人)经过某种机制(在这里是写在小纸条上的号码,喊号)找到等待该事件的人。
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来讲的。
阻塞调用是指调用结果返回以前,当前线程会被挂起,一直处于等待消息通知,不可以执行其余业务
。函数只有在获得结果以后才会返回。
有人也许会把阻塞调用和同步调用等同起来,实际上它们是不一样的。
对于同步调用来讲,不少时候当前线程可能仍是激活的,只是从逻辑上当前函数没有返回而已,此时,这个线程可能也会处理其余的消息
。还有一点,在这里先扩展下:(a) 若是这个线程在等待当前函数返回时,仍在执行其余消息处理,那这种状况就叫作同步非阻塞;
(b) 若是这个线程在等待当前函数返回时,没有执行其余消息处理,而是处于挂起等待状态,那这种状况就叫作同步阻塞;
因此同步的实现方式会有两种:同步阻塞、同步非阻塞;同理,异步也会有两种实现:异步阻塞、异步非阻塞;
非阻塞和阻塞的概念相对应,指在不能马上获得结果以前,该函数不会阻塞当前线程,而会马上返回
。虽然表面上看非阻塞的方式能够明显的提升CPU的利用率,可是也带了另一种后果就是系统的线程切换增长
。增长的CPU执行时间能不能补偿系统的切换成本须要好好评估
。
继续上面的那个例子,不管是排队仍是使用号码等待通知,若是在这个等待的过程当中,等待者除了等待消息通知以外不能作其它的事情,那么该机制就是阻塞的
,表如今程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。
相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的
,由于他(等待者)没有阻塞在这个消息通知上,而是一边作本身的事情一边等待。
可是须要注意了,同步非阻塞形式其实是效率低下的
,想象一下你一边打着电话一边还须要抬头看到底队伍排到你了没有。若是把打电话和观察排队的位置当作是程序的两个操做的话,这个程序须要在这两种不一样的行为之间来回的切换,效率可想而知是低下的;而异步非阻塞形式却没有这样的问题
,由于打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不一样的操做中来回切换。
效率是最低的,
拿上面的例子来讲,就是你专心排队,什么别的事都不作。
实际程序中:就是未对fd 设置O_NONBLOCK标志位的read/write 操做;
若是在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知)
,也就是领了一张小纸条,假如在这段时间里他不能离开银行作其它的事情,那么很显然,这我的被阻塞在了这个等待的操做上面;
异步操做是能够被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
好比select 函数,假如传入的最后一个timeout参数为NULL,那么若是所关注的事件没有一个被触发,程序就会一直阻塞在这个select 调用处
。
其实是效率低下的,
想象一下你一边打着电话一边还须要抬头看到底队伍排到你了没有,若是把打电话和观察排队的位置当作是程序的两个操做的话,这个程序须要在这两种不一样的行为之间来回的切换
,效率可想而知是低下的。
不少人会写阻塞的read/write 操做,可是别忘了能够对fd设置O_NONBLOCK 标志位,这样就能够将同步操做变成非阻塞的了
。
效率更高,
由于打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不一样的操做中来回切换
。
好比说,这我的忽然发觉本身烟瘾犯了,须要出去抽根烟,因而他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下(注册一个回调函数),那么他就没有被阻塞在这个等待的操做上面,天然这个就是异步+非阻塞的方式了。
若是使用异步非阻塞的状况,好比aio_*组的操做,当发起一个aio_read操做时,函数会立刻返回不会被阻塞,当所关注的事件被触发时会调用以前注册的回调函数进行处理
。
不少人会把同步和阻塞混淆,我想是由于不少时候同步操做会以阻塞的形式表现出来
,好比不少人会写阻塞的read/write操做,可是别忘了能够对fd设置O_NONBLOCK标志位,这样就能够将同步操做变成非阻塞的了。但最根本是由于没有区分这两个概念
,好比阻塞的read/write操做中,实际上是把消息通知机制和等待消息通知的状态结合在了一块儿
,在这里所关注的消息就是fd是否可读/写
,而等待消息通知的状态则是对fd可读/写等待过程当中程序(线程)的状态
。当咱们将这个fd设置为非阻塞的时候,read/write操做就不会在等待消息通知这里阻塞,若是fd不可读/写则操做当即返回。
一样的,不少人也会把异步和非阻塞混淆,由于异步操做通常都不会在真正的IO操做处被阻塞
,好比若是用select函数,当select返回可读时再去read通常都不会被阻塞,而是在select函数调用处阻塞
。
对上面所讲的概念再次进行一个场景梳理,上面已经明确说明,同步/异步关注的是消息通知的机制,而阻塞/非阻塞关注的是程序(线程)等待消息通知时的状态
。以小明下载文件打个比方,从这两个关注点来再次说明这两组概念,但愿可以更好的促进你们的理解。
同步体如今:等待下载完成通知;
阻塞体如今:等待下载完成通知过程当中,不能作其余任务处理;
同步体如今:等待下载完成通知;
非阻塞体如今:等待下载完成通知过程当中,去干别的任务了,只是时不时会瞄一眼进度条;【小明必需要在两个任务间切换,关注下载进度】
异步体如今:下载完成“叮”一声通知;
阻塞体如今:等待下载完成“叮”一声通知过程当中,不能作其余任务处理;
异步体如今:下载完成“叮”一声通知;
非阻塞体如今:等待下载完成“叮”一声通知过程当中,去干别的任务了,只须要接收“叮”声通知便可;【软件处理下载任务,小明处理其余任务,不需关注进度,只需接收软件“叮”声通知,便可】
也就是说,同步/异步是“下载完成消息”通知的方式(机制),而阻塞/非阻塞则是在等待“下载完成消息”通知过程当中的状态(能不能干其余任务)
,在不一样的场景下,同步/异步、阻塞/非阻塞的四种组合都有应用。
因此,综上所述,同步和异步仅仅是关注的消息如何通知的机制,而阻塞与非阻塞关注的是等待消息通知时的状态
。也就是说,同步的状况下,是由处理消息者本身去等待消息是否被触发,而异步的状况下是由触发机制来通知处理消息者
,因此在异步机制中,处理消息者和触发机制之间就须要一个链接的桥梁
:
在银行的例子中,这个桥梁就是小纸条上面的号码。
在小明的例子中,这个桥梁就是软件“叮”的声音。
最后,请你们注意理解“消息通知机制”和“等待消息通知时的状态”这两个概念,这是理解四个概念的关键所在。