前言
在日常的开发中,经常出现同步、异步、阻塞和非阻塞等概念。有些人搞不清楚什么代码是同步,什么代码是异步。有些人说我用异步了啊,为什么效率还是没提高呢?也许你是用异步了,但是可能是异步阻塞了。有些人一听说异步好,就不管三七二十一,所有方法全部改成异步,然后就会产生新的问题。归根结底还是对同步、异步、阻塞和非阻塞的概念不理解。
那究竟什么是同步、异步、阻塞和非阻塞呢?我决定尝试用比较通俗的例子举例来解释一下,于是便有了这篇文章。
同步
概念
什么是同步?同步就是一个任务的完成依赖于另外一个任务,只有被依赖的任务完成后,那么依赖的任务才能继续完成。
这是一种非常可靠的任务序列,要么都成功,要么都失败。
就和以前的瀑布式开发一样,你要做一个项目,一定要严格按照需求、分析、设计、编码、测试的步骤顺序执行,如果其中有一个问题卡住了,那么后边就无法继续,如果其中有一个环节是搞错了,那么这个项目也就可能死掉了。
同步又分同步阻塞和同步非阻塞,接下来将依次举例解释。
同步阻塞
同步阻塞就是调用方在等待另一个任务的结果返回来之前,什么事也没做,就是死等,直到返回,而且具体要等待的结果是用来干嘛的,如果不返回,是不知道的。
举例:
快到饭点了,老王很饿,来到了一家饭店,老王跟老板说来份蛋炒饭。
然后老板就是开始做,而老王就一直站在出饭的窗口等着,期间什么也没干,就是等着,直到老板把饭做好。
说明:
-
数据调用方:老王 (主线程)
-
数据持有方:饭店老板
-
要获得的数据:蛋炒饭
-
同步:老王等着老板把饭做好
-
阻塞:老王在等着,期间什么也没做
这种调用方式很常见,这是以前普遍的方式,流水线式调用。
如果老王就这样等着,那么效率是很低的。
同步非阻塞
同步非阻塞就是,你在等一件事A的结果时,可以继续去干另外一件事B。但是你需要时不时的回来瞧一瞧A的结果是否返回了。
程序上通常都是用轮询去获取A事件是否完成并返回结果。
举例:
快到饭点了,老王很饿,来到了一家饭店,老王跟老板说来份蛋炒饭。
然后老板就是开始做,而老王呢,就跑到饭店的门头透透气,顺便刷刷微博,但是老王需要时不时的跑进去问问老板饭好了没有
说明:
-
数据调用方:老王 (主线程)
-
数据持有方:饭店老板
-
要获得的数据:蛋炒饭
-
同步:老王等着老板把饭做好
-
非阻塞:老王等老板把饭做好,但是并不是站着死等,而是出门透透气,刷刷微博
这种调用方式,一般在同步操作中,中间需要处理一个比较耗时的操作时,通常是另外开辟一个线程去处理这个比较耗时的操作,但是后边需要自己去轮询另外一个线程的执行结果是否完成。
就比如上边,饭店老板做饭比较耗时,老王并没有一直等着,而是出门刷手机,但是饭有没有做好他需要时不时的跑进去问问,需要切换自己的位置。
异步
概念
异步就是调用方不需要一直等待被依赖的操作完成,可以继续干其他事情,当被依赖的操作完成后,会自动通知调用方。
调用异步操作时,被依赖的操作会直接返回一个任务或承诺给调用方,然后调用方会继续执行后边的操作。等被依赖的操作完成后,会通知调用方
日常生活中就很常见,比如:
领导给你安排一个任务,可能会说:小明你把xx项目的方案写一下,写完之后xx项目的相关人员一起去会议室开个会。小明肯定不能立即把方案写完,此时就会回答领导,“好的领导”。然后领导继续干自己的事,小明就去写方案,写完之后小明去通知领导,然后领导拿到方案后通知相关人员一起开会。
异步阻塞
异步阻塞就是虽然被依赖项完成时会通知调用方,但是调用方并没有继续去干其他事,而是依旧继续在那等着。
举例:
快到饭点了,老王很饿,来到了一家饭店,老王跟老板说来份蛋炒饭。
老板在点菜机上下了单,并给了老王一个号,告诉老王等饭做好后,会叫号通知的。
虽然会通知,但是老王很饿很着急,依旧站在出饭窗口等着。
说明:
-
数据调用方:老王 (主线程)
-
数据持有方:饭店老板
-
要获得的数据:蛋炒饭
-
异步:饭做好了会叫号通知的。
-
阻塞:虽然会通知,但是老王依旧站那等着饭做好,期间什么也没做。
目前,现实社会中大部分场景都有了叫号功能,不需要一直排队等着。但是也免不了有个别人比较着急,即便是会叫号也一直站旁边等着。
异步非阻塞
异步非阻塞就是被依赖项完成时会通知调用方,调用方无需等待被依赖项,可以继续干其他跟依赖项结果无关的事。
举例:
快到饭点了,老王很饿,来到了一家饭店,老王跟老板说来份蛋炒饭。
老板在点菜机上下了单,并给了老王一个单子,单子上是5号,告诉老王饭做好后,会叫这个号通知的。
然后,老王拿着号找了个位置,刷起了新闻,期间还出门打了个电话,处理了一些工作上的事。突然老王听到叫号机通知5号的饭做好了,然后老王去窗口拿自己的饭。
说明:
-
数据调用方:老王 (主线程)
-
数据持有方:饭店老板
-
要获得的数据:蛋炒饭
-
异步:饭做好了会叫号通知的。
-
非阻塞:因为饭做好后会叫号,所以老王并没有一直站那等着,期间找了个位置刷起了新闻,还出去接了个电话
这种效率是最高的,你做你的饭,我可以继续干不依赖于饭的事。你做好饭了叫我就行。
异步在目前的前后端开发中都挺常见的。
前端中通常会返回一个Promise(承诺)对象,.NET后端通常返回一个Task(任务),两个其实是一个意思。接下来以前端来举例例如:
//AService.js 有两个方法,一个是获取数据getData(),一个是做一些耗时操作doSomething()
async getData(){
return await GetMyData()//该方法需要查数据比较耗时
}
//该方法就写一些日志
async doSomething(){
await writeLog()
}
//B.vue页面
inint (){
// 1 异步执行某个操作
doSomething()
// 2 异步获取数据,然后对数据做xxx处理。代码的阅读和现实的语义是一样的。
getData().then(res => {
//可以针对res做相应的处理
})
//3
//... 不依赖于1、2的同步操作。
}
//上边doSomething()和getData().then()都会立即返回一个Promise(承诺),承诺会完成.
上边1、2都是瞬间完成的,返回了一个Promise(承诺)。既然叫承诺,那就很好理解了,我承诺会完成,但不是立即完成。
就比如开会的时候领导说,接下来安排一下任务:
- 张三,你把A项目的方案写一下,写完跟我讲一下,我们一起开会讨论一下,张三说”好的“。此时张三只是给领导了一个承诺,并没有立即完成。(异步)
- 李四,你等会把B项目部署一下,看看运行效果。李四说”好的“。此时李四也是一个承诺,并没有完成B项目部署。(异步)
- 然后领导将会议记录和任务安排发了给大家。(同步)
上边的3并不依赖于1、2,领导不需要非要等张三把A项目方案写好,也不需要等李四把B项目部署好,再将会议记录和任务安排发给大家。
如果您觉得这篇文章有帮助到你,欢迎推荐。如果您觉得哪里不对,也欢迎大家拍砖讨论。