美文网首页程序猿CodeEasejava核心知识
聊聊同步、异步、阻塞与非阻塞

聊聊同步、异步、阻塞与非阻塞

作者: 七寸知架构 | 来源:发表于2016-05-13 09:08 被阅读13195次

    近来遇到了一些常见的概念,尤其是网络编程方面的概念,如:阻塞、非阻塞、异步I/O等等,对于这些概念自己也没有太清晰的认识,只是很模糊的概念,说了解吧也了解,但是要让自己准确的描述概念方面的具体细节,却说的不那么准确,这也是自己在这几个方面也没有细细考究过的原因吧。经过看了些这几个概念的资料,发现同步、异步、阻塞、非阻塞的概念其实也并不难以理解,在此写下此文,欢迎拍砖,希望多多交流。

    1 同步与异步#

    首先来解释同步和异步的概念,这两个概念与消息的通知机制有关。也就是同步与异步主要是从消息通知机制角度来说的。

    1.1 概念描述##

    所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。

    所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列

    1.2 消息通知##

    异步的概念和同步相对。当一个同步调用发出后,调用者要一直等待返回消息(结果)通知后,才能进行后续的执行;当一个异步过程调用发出后,调用者不能立刻得到返回消息(结果)。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者

    这里提到执行部件和调用者通过三种途径返回结果:状态、通知和回调。使用哪一种通知机制,依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制

    1. 如果执行部件用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一种很严重的错误);

    2. 如果是使用通知的方式,效率则很高,因为执行部件几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。

    1.2 场景比喻##

    举个例子,比如我去银行办理业务,可能会有两种方式:

    1. 选择排队等候;

    2. 另种选择取一个小纸条上面有我的号码,等到排到我这一号时由柜台的人通知我轮到我去办理业务了;

    第一种:前者(排队等候)就是同步等待消息通知,也就是我要一直在等待银行办理业务情况;

    第二种:后者(等待别人通知)就是异步等待消息通知。在异步消息处理中,等待消息通知者(在这个例子中就是等待办理业务的人)往往注册一个回调机制,在所等待的事件被触发时由触发机制(在这里是柜台的人)通过某种机制(在这里是写在小纸条上的号码,喊号)找到等待该事件的人。

    2 阻塞与非阻塞#

    阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。

    2.1 概念描述##

    阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务。函数只有在得到结果之后才会返回。

    有人也许会把阻塞调用和同步调用等同起来,实际上它们是不同的。

    1. 对于同步调用来说,很多时候当前线程可能还是激活的,只是从逻辑上当前函数没有返回而已,此时,这个线程可能也会处理其他的消息。还有一点,在这里先扩展下:

    (a) 如果这个线程在等待当前函数返回时,仍在执行其他消息处理,那这种情况就叫做同步非阻塞;

    (b) 如果这个线程在等待当前函数返回时,没有执行其他消息处理,而是处于挂起等待状态,那这种情况就叫做同步阻塞;

    所以同步的实现方式会有两种:同步阻塞、同步非阻塞;同理,异步也会有两种实现:异步阻塞、异步非阻塞;

    1. 对于阻塞调用来说,则当前线程就会被挂起等待当前函数返回;

    非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。虽然表面上看非阻塞的方式可以明显的提高CPU的利用率,但是也带了另外一种后果就是系统的线程切换增加增加的CPU执行时间能不能补偿系统的切换成本需要好好评估

    2.2 场景比喻##

    继续上面的那个例子,不论是排队还是使用号码等待通知,如果在这个等待的过程中,等待者除了等待消息通知之外不能做其它的事情,那么该机制就是阻塞的,表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行。

    相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的,因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待。

    但是需要注意了,同步非阻塞形式实际上是效率低下的,想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有。如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;而异步非阻塞形式却没有这样的问题,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。

    3 同步/异步与阻塞/非阻塞#

    1. 同步阻塞形式

    效率是最低的,

    拿上面的例子来说,就是你专心排队,什么别的事都不做。

    实际程序中:就是未对fd 设置O_NONBLOCK标志位的read/write 操作;

    1. 异步阻塞形式

    如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;

    异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。

    比如select 函数,假如传入的最后一个timeout参数为NULL,那么如果所关注的事件没有一个被触发,程序就会一直阻塞在这个select 调用处

    1. 同步非阻塞形式

    实际上是效率低下的,

    想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。

    很多人会写阻塞的read/write 操作,但是别忘了可以对fd设置O_NONBLOCK 标志位,这样就可以将同步操作变成非阻塞的了

    1. 异步非阻塞形式

    效率更高,

    因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换

    比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下(注册一个回调函数),那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。

    如果使用异步非阻塞的情况,比如aio_*组的操作,当发起一个aio_read操作时,函数会马上返回不会被阻塞,当所关注的事件被触发时会调用之前注册的回调函数进行处理

    很多人会把同步和阻塞混淆,我想是因为很多时候同步操作会以阻塞的形式表现出来,比如很多人会写阻塞的read/write操作,但是别忘了可以对fd设置O_NONBLOCK标志位,这样就可以将同步操作变成非阻塞的了。但最根本是因为没有区分这两个概念,比如阻塞的read/write操作中,其实是把消息通知机制和等待消息通知的状态结合在了一起,在这里所关注的消息就是fd是否可读/写,而等待消息通知的状态则是对fd可读/写等待过程中程序(线程)的状态。当我们将这个fd设置为非阻塞的时候,read/write操作就不会在等待消息通知这里阻塞,如果fd不可读/写则操作立即返回。

    同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞,比如如果用select函数,当select返回可读时再去read一般都不会被阻塞,而是在select函数调用处阻塞

    4 小明的故事#

    对上面所讲的概念再次进行一个场景梳理,上面已经明确说明,同步/异步关注的是消息通知的机制,而阻塞/非阻塞关注的是程序(线程)等待消息通知时的状态。以小明下载文件打个比方,从这两个关注点来再次说明这两组概念,希望能够更好的促进大家的理解。

    1. 同步阻塞:小明一直盯着下载进度条,到 100% 的时候就完成。

    同步体现在:等待下载完成通知;

    阻塞体现在:等待下载完成通知过程中,不能做其他任务处理;

    1. 同步非阻塞:小明提交下载任务后就去干别的,每过一段时间就去瞄一眼进度条,看到 100% 就完成。

    同步体现在:等待下载完成通知;

    非阻塞体现在:等待下载完成通知过程中,去干别的任务了,只是时不时会瞄一眼进度条;【小明必须要在两个任务间切换,关注下载进度】

    1. 异步阻塞:小明换了个有下载完成通知功能的软件,下载完成就“叮”一声。不过小明仍然一直等待“叮”的声音(看起来很傻,不是吗)。

    异步体现在:下载完成“叮”一声通知;

    阻塞体现在:等待下载完成“叮”一声通知过程中,不能做其他任务处理;

    1. 异步非阻塞:仍然是那个会“叮”一声的下载软件,小明提交下载任务后就去干别的,听到“叮”的一声就知道完成了。

    异步体现在:下载完成“叮”一声通知;

    非阻塞体现在:等待下载完成“叮”一声通知过程中,去干别的任务了,只需要接收“叮”声通知即可;【软件处理下载任务,小明处理其他任务,不需关注进度,只需接收软件“叮”声通知,即可】

    也就是说,同步/异步是“下载完成消息”通知的方式(机制),而阻塞/非阻塞则是在等待“下载完成消息”通知过程中的状态(能不能干其他任务),在不同的场景下,同步/异步、阻塞/非阻塞的四种组合都有应用。

    所以,综上所述,同步和异步仅仅是关注的消息如何通知的机制,而阻塞与非阻塞关注的是等待消息通知时的状态。也就是说,同步的情况下,是由处理消息者自己去等待消息是否被触发,而异步的情况下是由触发机制来通知处理消息者,所以在异步机制中,处理消息者和触发机制之间就需要一个连接的桥梁

    在银行的例子中,这个桥梁就是小纸条上面的号码。

    在小明的例子中,这个桥梁就是软件“叮”的声音。

    最后,请大家注意理解“消息通知机制”和“等待消息通知时的状态”这两个概念,这是理解四个概念的关键所在。

    相关文章

      网友评论

      • 今生愿为一个橙子:非常非常的好,期待成为跟你一样的大神!小白奋斗中!
      • 3a577b5e6a02:从博主的文章总结下:
        1. 同步与异步是站在消息通知机制角度来说的(同步可能需要时刻去关心询问线程处理结果,异步注册了回调机制,无需关心)
        2.阻塞和非阻塞是站在线程等待调用结果的线程状态这个角度来说的,阻塞则是线程挂起等待调用结果返回;非阻塞是在等待结果的过程中,线程任然是活动状态,可能处理其他的任务罢了。
        七寸知架构:@星辰醉天河 :+1:
      • NOT_FOUND:先看例子,再回去看概念介绍,要容易理解
        七寸知架构:@名字永远是trouble :+1:
      • 06225f9aede0:比较难理解的是异步阻塞,在io模型里, 其实分两阶段: 一个是等有io读写的事件通知,一个是在读写io时的方式,假设这个阶段时间很长, 异步非阻塞,是两个阶段都可以去干其它的,而异步阻塞是等得时候不能去干其它的,但读写io的时候可以去干其它的。
        06225f9aede0:等事件和处理事件的过程,假设在处理事件很耗时。
      • c96e2af6dd7a:写得非常好, 看完了豁然开朗, 加上自己的理解, 其实同步就是"主动"做事, 异步就是"被动"做事.
        比如排队的情况: 同步需要自己主动盯着, 异步会有人来叫你, 不管是阻塞还是非阻塞都是.
        七寸知架构:@stephenliu_1944 洞察真理
      • 2f3bd6a9ec9a:今天看同步,异步,阻塞,非阻塞看了一天,都说的模棱两可,就是老哥这边通俗易懂啊;
        消息通知的机制,等待消息通知的状态看了后,对这四个概念有种豁然开朗的感觉!
        七寸知架构:@II_463f :+1:
      • 小肥的胖:感谢分享!自己一直就在纠结在无法用生动的例子解释同步非同步和阻塞非阻塞,看了您的例子豁然开朗。
      • a9a0d4e5ebab:简洁明了
      • 一许青衫一:老哥,写的很明白!
      • 咸鱼翻身ing:大佬写的通俗易懂,给个赞
      • dandan的微笑:看了这么多,这篇的小明最通俗易懂
      • zy_cf6d:看了感觉很清楚,在不了解底层的原理的情况下,还能把我说明白。好文,登陆赞一个!
      • 8c8c2457822d:讲得很透彻!谢谢分享!学习了!
      • codeT:浅显易懂,赞
      • goo_3f58:给大神跪了
      • cool_wier:写的很棒,通俗易懂。有个例子我觉得不是很恰当,首先同步与异步主要区别,是是否导致请求进程阻塞。也就是调用者是否“主动”在关注这个结果,你的小明例子>异步阻塞,它应该属于同步,因为在等待这个叮咚的结果,属于主动。关于同步与异步,阻塞与非阻塞,如果把处理两阶段提前进行说明,会更好理解,因为同步与非同步的范畴更大些,而是否阻塞主要发生在第一阶段,这个在Unix网络编程中,有很好的说明,前期的介绍过于复杂,如果上图直接分析,再讲例子会很好。写的很不错了,加油
        zhanglbjames:等待叮咚的结果:等待被通知,应该是被动的,如果是主动,就会轮询叮咚的结果
      • 真实的追梦者:写的很好。
      • 9b8dd6db8b4e:还有上面你说到的select函数,并不是异步的。我个人认为,他是多路复用IO的模式,由一个线程管理多个通道,至于是否阻塞,完全是看程序的实现。 关于异步的概念,我这边特指异步IO,异步IO需要操作系统支持,具体就是数据从内核空间复制到用户空间这一步,由内核线程完成。所以异步需要操作系统系统支持。 其余的由用户线程完成复制数据这个动作的,都叫同步。我参考的是 unix网络编程
        9b8dd6db8b4e:@三十岁大叔 right!
        尼古拉斯二哥:他异步是因为结果是内核发送给调用方的,select是被动接受消息通知
      • 9b8dd6db8b4e:感谢作者回复了我的评论,看了这么多博客 回复评论的实在不多。另外我觉得,同步和异步是一个概念,阻塞和非阻塞是另一个概念,这两者之间没有什么关系 ,不存在有什么同步非阻塞一说吧,我个人觉得,同步,就一定是阻塞的,异步就一定是非阻塞的。
        七寸知架构:@眼眼眼眼眼_374 这两种自己都可以实现
        9b8dd6db8b4e:@书海陶然 emm,那你觉得同步非阻塞 ,和异步阻塞。 在Java中,有什么例子是这样的一个模型吗?
        七寸知架构:@眼眼眼眼眼_374 同步与异步代表是获取消息的方式,同步是主动获取,异步是被动通知,而阻塞与非阻塞是等待消息时的状态,阻塞代表不执行其他任务,非阻塞可以执行其他任务。同步非阻塞,主动获取消息但等待消息期间可执行其他任务。异步阻塞,被动通知消息但等待消息期间不执行其他任务。
      • 1519f8ccc7b0:这两组概念是在不同角度下看待问题时的产物,可以从这两个角度来看,是不是更简单些。
        若将主体看做线程,客体看做业务逻辑。
        同步与异步代表的角度就是:主体获取客体执行状态(结果)的机制,主动获取就是同步,被动等待通知就是异步;
        阻塞与非阻塞代表的角度就是:主体是否被客体所独占,独占就是阻塞,非独占(可以做其他业务逻辑)就是非阻塞。
        七寸知架构:@沃夫卡姆大叔 可以,大体是这个意思
      • 831e5a34342b:"非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回" 解释非阻塞的时候,说立即返回,这样解释的话,那不是就等于说非阻塞一定是异步吗,可是后面又有同步非阻塞,所以这边是不是解释有误
        尼古拉斯二哥:阻塞:就是read()函数, 什么都不干,就等2秒,数据返回。
        非阻塞 :比如调用read()函数, 需要2秒读完数据, 但是马上返回。

        同步:请求方(调用方)主动去查看结果。
        异步:请求方(调用方)被动获得结果。

        同步阻塞:调用方等待read()返回。
        同步非阻塞:调用方就是需要我用个for((n=read()) > 0) 去轮询read()函数是不是完事了。

        异步阻塞:调用方去调用read(), 同时注册一个回调函数,或者信号捕捉函数,等read()读完了, 就出发这个机制,执行处理函数,
        但是在这个2秒中内,我什么都不干,就等着他处理,比如select函数。异步等待三次握手成功的连接,但是在三次握手之间,select是阻塞的。

        异步非阻塞:就是调用方调用read(),注册处理函数,然后read()完事了, 直接去执行处理函数,但是在read()2秒钟期间, 调用方,又去执行其他的任务了。
        就像我调用了, 剩下的读取,读取完处理函数, 都和我没关系了。比如epoll反应堆模型。
        七寸知架构:非阻塞 立刻返回 不代表是异步 立刻返回 可能还是同步 ,同步 异步 与 阻塞 非阻塞 分别代表的是两件事情 前者是消息通知的方式 后者是等待消息的状态。
      • 会飞的猪姥姥:感觉作者也是没有特别清楚第三种模型,这篇文章和上篇文章的说法有矛盾之处
      • 看海8801:写的太好了!
      • 小程有话说:写的很棒,深入浅出。:clap:
      • 31d46a7dbc92:有没有可能出现异步阻塞?非阻塞一定是异步吗?
        七寸知架构:@灬卟離卟棄d愛 :+1:
        Do_you:肯定有异步阻塞的情况,异步同步和阻塞非阻塞没关联,按照作者的例子,就是你去银行办业务,取票了等待系统喊到你的票不是在排队,就是异步的。但是,如果你在等待期间什么事情都不干,傻傻的等着,可以理解成阻塞的。如果你等待的期间玩着手机聊着天,那就是非阻塞的,你等待期间还干了其他事情。
      • allanruin:学习了,楼主文章太好,真正通俗易懂,让我明白饿了同步/异步,阻塞/非阻塞的区别!
      • 假装有文化_:感觉异步非阻塞也可以描述成一种“同步”。
      • 6c905e79f2c3:可以交个朋友吗?希望在学术方面可以有所交流,我是个linux的初学者。
      • 一个不熬夜的孩子:很棒,顺便梳理了这一块知识点。瞬间清晰了很多
      • 橙子ccc123:微信公众号是多少
      • 张小刀:内容很好,但排版让人抓狂~~~看着看着就跳出了,然后看了好几遍才看完~~
        统一字体就行,不用一会儿大一会儿小的。
      • 程序员钙片吃多了:提个观点哈,不太赞同“如果执行部件用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一种很严重的错误)”这句话所处的位置,按作者意思这是一个异步过程。其实我认为这属于一种同步过程,毕竟这个只是状态变化,而且没有通知的含义,需要调用者去查询状态,然后去获取实际数据。这个和异步还是有点差别,我理解的异步一般是数据或等操作完成后,把调用者所需的东西主动上报给调用者(甚至这其中都不应该包含内核拷贝到应用进程的过程)。

        当然,我这里思考的是根据IO的概念理解的,不知我是否有误解
        JasonC30:@mood_if 同意您的说法!
        七寸知架构:@mood_if :+1:
        6c905e79f2c3:我可能比较赞同作者的说法,所谓的同步(无论是同步阻塞也好还是同步非阻塞也好)就是必须等待被调用者处理结束后,才可以继续往下执行,只不过再此过程中调用者所处的状态不同,作者此处描述的是调用者发起调用后,调用者并没有被挂起,但是也没有继续往下执行,而是通过发起系统调用,不断的询问被调用者的状态(或者说被调用者处理调用者给予的任务处理的如何了),而所谓的真正的异步模式,调用者是完全被释放的(即使是所谓的事件驱动机制也没有达到真正的异步),通俗的说就是调用者一旦发起调用后,剩下的事务就不需要调用者参与了。这是个人的理解,仅供参考,如果有什么理解误区,欢迎指出。
      • skybosi:可怜的小明,被玩坏了! :smile:
      • 89b859365bc3:阻塞,非阻塞是否还要考虑cpu状态,如果线程采用spin_lock检查状态,cpu处在busy waiting的状态,也算非阻塞吧?
        七寸知架构:@tuijunxi 线程什么也没干 一直干等 就是阻塞
        b9c3fa53d4ab:spin_lock是自旋锁,如果线程采用spin_lock检查状态就是在忙等,该线程还在占用cpu资源,并没有被阻塞,当然算是非阻塞
        七寸知架构:@yyai326 阻塞与非阻塞是对于线程而言的,与CPU状态无关。
      • 城南道:写的很深刻,通俗易懂,膜拜大神⊙▽⊙
      • 七寸知架构:多看 多想 Why How What :smile:
      • iOS_愛OS:想了解下你是怎么查找和总结到这些知识的,东西写的很棒,想跟你学习下怎么写技术文章
        七寸知架构:@iOS_愛OS 多看 多想 Why How What :smile:
      • b4fcf592b9d1:大神,在微博,微信公众号上经常看到你写的文章,尤其伯乐在线
        七寸知架构:@飞空流雪 :+1:

      本文标题:聊聊同步、异步、阻塞与非阻塞

      本文链接:https://www.haomeiwen.com/subject/amgprttx.html