理解同步、异步、阻塞和非阻塞

1.前言

同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。

阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。

阻塞是使用同步机制的结果,非阻塞则是使用异步机制的结果。

因其采用异步非阻塞的模型而构建,Node.js 得以能充分利用 CPU 资源,具有极强的处理高并发请求的能力。

首先讨论下使用事件驱动,异步编程的优点:

充分利用了系统资源,执行代码无须阻塞等待某种操作完成,有限的资源可以用于其他的任务。其非常适合于后端的网络服务编程。

在服务器开发中,并发的请求处理是个大问题,阻塞式的函数会导致资源浪费和时间延迟。通过事件注册、异步函数,开发人员可以提高资源的利用率,性能也会改善。

其nginx和node.js处理并发都是采用的事件驱动异步非阻塞模式。其中nginx中处理并发用的是epoll,poll,queue等方式,node.js使用的是libev,它们对大规模的HTTP请求处理的都很好。

2.阻塞和非阻塞

从简单的开始,我们以经典的读取文件的模型举例。(对操作系统而言,所有的输入输出设备都被抽象成文件。)

在发起读取文件的请求时,应用层会调用系统内核的 I/O 接口。

如果应用层调用的是阻塞型 I/O,那么在调用之后,应用层即刻被挂起,一直出于等待数据返回的状态,直到系统内核从磁盘读取完数据并返回给应用层,应用层才用获得的数据进行接下来的其他操作。

如果应用层调用的是非阻塞 I/O,那么调用后,系统内核会立即返回(虽然还没有文件内容的数据),应用层并不会被挂起,它可以做其他任意它想做的操作。(至于文件内容数据如何返回给应用层,这已经超出了阻塞和非阻塞的辨别范畴。)

这便是(脱离同步和异步来说之后)阻塞和非阻塞的区别。总结来说,是否是阻塞还是非阻塞,关注的是接口调用(发出请求)后等待数据返回时的状态。被挂起无法执行其他操作的则是阻塞型的,可以被立即「抽离」去完成其他「任务」的则是非阻塞型的。

在这里插入图片描述

阻塞调用:比如 socket 的 recv(),调用这个函数的线程如果没有数据返回,它会一直阻塞着,也就是 recv() 后面的代码都不会执行了,程序就停在 recv() 这里等待,所以一般把 recv() 放在单独的线程里调用。

非阻塞调用:比如非阻塞socket 的 send(),调用这个函数,它只是把待发送的数据复制到TCP输出缓冲区中,就立刻返回了,线程并不会阻塞,数据有没有发出去 send() 是不知道的,不会等待它发出去才返回的。

3.同步和异步

阻塞和非阻塞解决了应用层等待数据返回时的状态问题,那系统内核获取到的数据到底如何返回给应用层呢?这里不同类型的操作便体现的是同步和异步的区别。

对于同步型的调用,应用层需要自己去向系统内核问询,如果数据还未读取完毕,那此时读取文件的任务还未完成,应用层根据其阻塞和非阻塞的划分,或挂起或去做其他事情(所以同步和异步并不决定其等待数据返回时的状态);如果数据已经读取完毕,那此时系统内核将数据返回给应用层,应用层即可以用取得的数据做其他相关的事情。

而对于异步型的调用,应用层无需主动向系统内核问询,在系统内核读取完文件数据之后,会主动通知应用层数据已经读取完毕,此时应用层即可以接收系统内核返回过来的数据,再做其他事情。

这便是(脱离阻塞和非阻塞来说之后)同步和异步的区别。也就是说,是否是同步还是异步,关注的是任务完成时消息通知的方式。由调用方盲目主动问询的方式是同步调用,由被调用方主动通知调用方任务已完成的方式是异步调用。

在这里插入图片描述

附注:同步、异步、阻塞和非阻塞总结:

同步、异步:

概念:消息的通知机制
解释:涉及到IO通知机制;所谓同步,就是发起调用后,被调用者处理消息,必须等处理完才直接返回结果,没处理完之前是不返回的,调用者主动等待结果;所谓异步,就是发起调用后,被调用者直接返回,但是并没有返回结果,等处理完消息后,通过状态、通知或者回调函数来通知调用者,调用者被动接收结果。

阻塞、非阻塞:

概念:程序等待调用结果时的状态
解释:涉及到CPU线程调度;所谓阻塞,就是调用结果返回之前,该执行线程会被挂起,不释放CPU执行权,线程不能做其它事情,只能等待,只有等到调用结果返回了,才能接着往下执行;所谓非阻塞,就是在没有获取调用结果时,不是一直等待,线程可以往下执行,如果是同步的,通过轮询的方式检查有没有调用结果返回,如果是异步的,会通知回调。

4.同步、异步、阻塞和非阻塞比较

同步的定义看起来跟阻塞很像,但是同步跟阻塞是两个概念,同步调用的时候,线程不一定阻塞,调用虽然没返回,但它还是在运行状态中的,CPU很可能还在执行这段代码,而阻塞的话,它就肯定不在CPU中跑这个代码了。这就是同步和阻塞的区别。同步是肯定可以在,阻塞是肯定不在。

异步和非阻塞的定义比较像,两者的区别是异步是说调用的时候结果不会马上返回,线程可能被阻塞起来,也可能不阻塞,两者没关系。非阻塞是说调用的时候,线程肯定不会进入阻塞状态。

上面两组概念,就有4种组合。

1.同步阻塞调用:得不到结果不返回,线程进入阻塞态等待。

2.同步非阻塞调用:得不到结果不返回,线程不阻塞一直在CPU运行。

3.异步阻塞调用:去到别的线程,让别的线程阻塞起来等待结果,自己不阻塞。

4.异步非阻塞调用:去到别的线程,别的线程一直在运行,直到得出结果。

5.Node.js的异步非阻塞模型

完整来说,一个最高效且理想的文件读取异步非阻塞模型应该是这样的:应用层发起调用后系统内核立即返回(还没有文件内容数据),应用层继续做其他无关的事情,在系统内核从磁盘读取完数据之后主动通知应用层任务已完成,应用层此时接收系统内核返回的数据,然后继续做其他相关或不相关的事情。

可以看到,在这个模型中,没有无谓的挂起、休眠与等待,也没有盲目无知的问询与检查,应用层做到不等候片刻的最大化利用自身的资源,系统内核也十分「善解人意」的在完成任务后主动通知应用层来接收任务成果。

Node.js 是不是就是这样实现的呢?是,也不是。

现实总是比理想骨感,系统内核并没有理想中那样「善解人意」。异步模型的内核调用在各个平台上实现不一,而且各有各的问题,所以实际上, Node.js 其实是借助多线程来模拟实现了上述理想的异步非阻塞模型。

有人可能有疑问,前面不是说 Node.js 是单线程的吗?

实际上单线程是对用户(使用 Node.js 进行上层开发的程序员,而不是开发 Node.js 的人员)而言的。Node.js 在底层对多个 I/O 操作是借助多线程实现异步非阻塞的,具体来说,Node.js 总是存在一个主线程,用来管理调度 I/O 线程并进行运算,而其他的线程都是 I/O 线程。I/O 线程在主线程的调度下与系统内核进行交互完成完成 I/O 操作并把数据返回给主线程,而主线程对 I/O 线程的调度就完全是上述异步非阻塞的(至于 I/O 线程是异步还是同步、阻塞还是非阻塞,已经不重要了,因为它不影响主线程的效率,只要它能按时返回预期的数据就行)。我们平时所说的 Node.js 是单线程的,就是指 Node.js 的主线程。I/O 线程完全是对用户屏蔽的,所以用户根本无需关心。

这也解释了为什么我们要避免书写计算密集型或者阻塞的代码,一旦主线程被阻塞,那整个应用就是真的都被阻塞了。

6. 场景举例与总结

最后,再来举一个我们日常的例子来加深对这几个概念的理解。

假设小明需要在网上下载一个软件:

  • 如果小明点击下载按钮之后,就一直干瞪着进度条不做其他任何事情直到软件下载完成,这是同步阻塞;
  • 如果小明点击下载按钮之后,就一直干瞪着进度条不做其他任何事情直到软件下载完成,但是软件下载完成其实是会「叮」的一声通知的(但小明依然那样干等着),这是异步阻塞;(不常见)
  • 如果小明点击下载按钮之后,就去做其他事情了,不过他总需要时不时瞄一眼屏幕看软件是不是下载完成了,这是同步非阻塞;
  • 如果小明点击下载按钮之后,就去做其他事情了,软件下载完之后「叮」的一声通知小明,小明再回来继续处理下载完的软件,这是异步非阻塞。

相信看完以上这个案例之后,这几个概念已经能够分辨得很清楚了。

总的来说,同步和异步关注的是任务完成消息通知的机制,而阻塞和非阻塞关注的是等待任务完成时请求者的状态。

7.并行和并发

并发是指一个时间段内,有几个程序都在同一个CPU上运行,但任意一个时刻点上只有一个程序在处理机上运行。

并行是指一个时间段内,有几个程序都在几个CPU上运行,任意一个时刻点上,有多个程序在同时运行,并且多道程序之间互不干扰。

两者区别如下图:

在这里插入图片描述

在这里插入图片描述

并发是多个程序在一个CPU上运行,CPU在多个程序之间快速切换,微观上不是同时运行,任意一个时刻只有一个程序在运行,但宏观上看起来就像多个程序同时运行一样,因为CPU切换速度非常快,时间片是64ms(每64ms切换一次,不同的操作系统有不同的时间),人类的反应速度是100ms,你还没反应过来,CPU已经切换了好几个程序了。

并行是多个程序在多个CPU上同时运行,任意一个时刻可以有很多个程序同时运行,互不干扰。

CPU调度策略:
1.先来先服务-时间片论转调度
2.优先级调度
3.最短作业优先
4.最高响应比优先
5.多级反馈队列调度

参考文章:

http://maples7.com/2016/08/24/understand-sync-async-and-blocking-non-blocking/
https://blog.csdn.net/hguisu/article/details/7453390
https://blog.csdn.net/linhuaiyang/article/details/68483222?#commentBox
https://www.cnblogs.com/-900401/p/4015048.html
https://blog.csdn.net/sinat_35512245/article/details/53836580

还可以参考:https://blog.csdn.net/ZhengKehang/article/details/80624042

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
同步非阻塞异步非阻塞是两种不同的函数调用方式。 同步非阻塞是指在发起函数调用后,调用方不会等待函数的结果返回,而是立即继续执行后续的代码。这意味着调用方无需等待函数的执行结果,可以继续处理其他任务。当函数执行完毕后,调用方可以通过轮询或者回调等方式来获取函数的结果。同步非阻塞的优点是能够提高系统的并发性和响应性。 异步非阻塞是指在发起函数调用后,调用方也不会等待函数的结果返回,但是与同步非阻塞不同的是,异步非阻塞会通过回调、事件通知等方式来通知调用方函数的执行结果。调用方可以继续执行其他任务,并在合适的时机处理函数的返回结果。异步非阻塞的优点是能够提高系统的并发性和性能,减少资源的浪费。 总结来说,同步非阻塞异步非阻塞都是在函数调用时不会发生线程阻塞的情况下继续执行后续代码,但区别在于同步非阻塞需要主动轮询或回调来获取函数的结果,而异步非阻塞则通过回调或事件通知等方式传递函数的执行结果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [深入理解同步阻塞同步非阻塞异步阻塞异步非阻塞](https://blog.csdn.net/wangpaiblog/article/details/117236684)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值