同步(Synchronous)异步(Asynchronous) 阻塞(Blocking)和非阻塞(Non-blocking)的概念

本文深入解析了同步与异步、阻塞与非阻塞的概念及其应用场景,对比了不同调用方式的特点与优劣,帮助读者理解如何在软件设计中合理选择。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、同步~异步~阻塞~非阻塞

    同步(Synchronous)和异步(Asynchronous)的概念本来来自通信领域:首先是通信的同步,主要是指客户端在发送请求后,必须得在服务端有回应后才发送下一个请求,所以这个时候的所有请求将会在服务端得到同步;其次是通信的异步,指客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求,这样对于所有的请求动作来说将会在服务端得到异步,这条请求的链路就像是一个请求队列,所有的动作在这里不会得到同步的。


    阻塞(Blocking)和非阻塞(Non-blocking)主要是指某个程序模块、进程是否占用了系统的某种资源(各种硬件资源和软件资源,甚至包括时间等等)而影响其它程序模块、进程无法运行或者处理它的任务。如果影响了,就是阻塞;如果没有影响,就是非阻塞。

    很多人会把“同步/异步”和“阻塞/非阻塞”的概念搞混淆,总认为同步就是阻塞,异步就是非阻塞。从上面的描述可以看出,其实“同步/异步”和“阻塞/非阻塞”之间是没有任何关系的。

   

二、 同步调用~异步调用~阻塞调用~非阻塞调用

    在实际中,我们说“同步和异步”,其实往往指的是函数调用的“同步和异步”,也就是“同步调用和异步调用”。所谓同步调用,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。异步调用的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果,但调用者会立即从被调用者返回。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。


    阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

    这个时候又有很多人会把“同步调用和异步调用”和“阻塞调用和非阻塞调用”搞混淆,把同步调用等同于阻塞调用,把异步调用等同于非阻塞调用,其实这是不对的。


    对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。而对于阻塞调用来说,当前线程会立即挂起,硬要等到要做的事情完成才会退出。

    对于异步调用来说,它往往只是Assign一个Task给被调用部件,被调用部件返回的结果是接不接受调用者的Task请求(如果太忙,或者任务队列满等原因会导致不接受请求),如果它接受你的请求,等到它做完后会想办法告诉调用者。而对于非阻塞调用来说,就是在调用的时候,如果被调用者能完成,就直接完成并告诉你完成了,如果它完不成就会告诉被调用者它无法完成,并告诉调用者它完不成的原因(当然,有些情况会省略这一步,一种情况是调用者不在意是什么原因导致执行部件无法完成;还有一种情况是只有一种原因导致执行部件无法完成,所以不说调用者也知道)。


    我们可以将这四种调用方式打几个粗浅的比喻:


    同步调用:“一定要把这个事情搞定!要人给人,要钱给钱!” 被调用者费了九牛二虎之力后虚弱的回答: “老大,事情摆平了……”


    异步调用:“嗯,你把这个事情处理一下。”被调用者回答TRUE:“收到,放心吧,老大,搞定后我会通知你的。”回答FALSE:“老大,现在我忙死了!等我缓口气行不行?”


    阻塞调用:“这段时间你就专心搞这个事情,不惜一切代价把这个事情搞定!等到天荒地老也要搞定!在线等!”被调用者求爷爷告奶奶四处求人终于把问题搞定后很委屈的回答:“老大,事情摆平了~~~”


    非阻塞调用:“嗯,你把这个事情处理一下。”被调用者一件一件的检查需要办完这件事情的资源,如果发现某种资源不够,就回答FALSE:“老大,XXXX没有,我搞不定。”如果整个检查下来发现资源都有,于是做完,并很干脆的回答TRUE:“老大,事情摆平了!”

 

三、异步调用结果的返回

    当执行部件执行任务完毕后(任务不一定成功完成),就需要将结果告诉调用者,好让调用者进行善后处理。前面说了,异步调用的结果,通过状态(State)、通知(Notify)和回调(Callback)来通知调用者。这三种方法大致如下:


    状态:当执行部件执行任务完毕后,通过设置某些全局变量来传递信息,被调用者在主程序中查询这些信息,并根据这些信息作出善后处理。在我们的程序中,状态机(State Machine)就是这种方法的典型体现。


    通知:当执行部件执行任务完毕后,通过某种通讯方法通知调用部件。在我们程序中,8032发UOP后,RISC处理完毕后的Notify就是这种方法的典型体现。


    回调:调用者在Assign Task的同时,除了任务,还会传递一个回调函数到执行部件,执行部件会通过函数指针记录这个回调函数;执行部件执行任务完毕后,会通过函数指针来调用这个回调函数。在我们的程序中,播放USB中的Divx文件时,读文件内容采用的就是这种方法。


    对于这三种方法,我们也可以打一个比较粗浅的比喻:


    状态:领导要下属做某件事情,下属接过任务开始做……领导过一段时间就跑过去问:搞定没有?回答:没有搞定;领导过一段时间又跑过去问:搞定没有?回答:没有搞定……领导跑过去问:搞定没有?回答:搞定了!


    通知:领导要下属做某件事情,当下属做完以后,打电话告诉领导:“我搞定了。”


    回调:领导要下属做某件事情,在交代事情的时候,还给他一个“锦囊妙计”,当下属做完以后,打开“锦囊妙计”,将善后事宜一一处理完毕。

    我们可以看出,状态的方法比较简单,也比较容易实现,并且不容易出错,可读性相对也好;但是它的实时性和效率没有通知和回调的方法好。

 

四、各种调用的进一步思考

    阐述到这里,我想我们有必要停下来思考一些东西,那就是这些不同的调用有哪些优缺点,应该应用在哪些场合?


    很多人认为异步调用比同步调用好;非阻塞调用比阻塞调用好;这也是不对的。个人一直认为:没有最好的,只有最适合的。同步调用和阻塞调用,程序相对简单,而且可读性强,移植性好,容易实现,容易维护……还有在一些特殊情况,必须使用同步调用和阻塞调用(不使用的话,其它的事情就没有办法做,比如初始化)。相对于同一个Task,异步调用和非阻塞调用并不能加快这个Task的执行速度和效率;相反,它们反而会降低这个Task的执行速度和效率!异步调用和非阻塞调用是站在整个系统的层面上考虑问题的,它们的目的在于使得整个系统运行的性能效率提高,各个部件之间运行配合更加协调。这才是它们真正的优点。所以我们在想把某个模块从同步/阻塞方式改为异步/非阻塞方式的时候就要考虑是否真的能够达到预定的目的。

### 阻塞非阻塞同步异步的定义、区别及相互关系 #### 定义 - **阻塞**是指在执行某些操作(如I/O操作)时,当前线程会被挂起并进入等待状态,直到该操作完成才会恢复运行[^1]。 - **非阻塞**则是指即使某个操作尚未完成,也不会使当前线程停止工作,而是立即返回一个结果或者允许程序继续执行其他任务[^1]。 - **同步**指的是发起请求的一方需要等到整个过程结束才能继续后续的操作,在此期间可能处于等待状态[^2]。这意味着如果采用同步模式,则无论是否发生阻塞现象,都意味着只有当目标事件完成后才可以进行下一步行动。 - **异步**表示一旦启动了一个耗时较长的任务之后就可以马上去做别的事情而不必关心它什么时候能够结束; 当这个长时间运行的工作最终完成了以后再通过某种机制(比如回调函数)告知原来的调用者已经完毕了. #### 区别 | 特性 | 描述 | |------------|----------------------------------------------------------------------------------------| | **阻塞 vs 非阻塞** | - 阻塞会让进程暂停直至特定条件满足。<br>-非阻塞则会立刻给出反馈或让应用程序自行查询进度。 | | **同步 vs 异步** | - 同步强调按顺序逐一完成每一步骤,并且可能会经历延迟期。<br>- 反之,异步允许多项活动并发开展,无需遵循严格的先后次序 。| #### 相互关系 尽管这两个维度经常被混淆讨论在一起, 实际上它们描述的是不同层面的行为特征: - 同步模型下既可能存在也不存在阻塞行为——例如标准库中的`read()`方法通常是同步而且默认情况下也是阻塞式的;但是我们可以通过设置超时参数等方式将其转变为一种伪“非阻塞”的形式 (即定期检查是否有新输入到来而不是无限期地停留). - 对于异步而言,默认倾向于表现为非阻塞特性,因为它往往依赖事件循环或者其他类似的调度框架来管理多个未决事项之间的切换时机. 不过理论上仍然存在所谓的‘异步却仍需主动轮询’的情况,这实际上就是把原本应该由底层自动触发的通知交给了高层手动去探测. 另外值得注意的一个概念差异体现在如何得知某件事务的结果方面: - 在传统的同步设计里边,一般直接获取返回值即可知道答案; - 然而在多数现代异步解决方案当中,则更多依靠注册好的监听器或者是传递过来专门用于接收消息的对象实例等形式间接获知进展状况以及最后成果. ```python import asyncio async def async_example(): await asyncio.sleep(1) # 这是一个典型的异步非阻塞的例子 print("Non-blocking asynchronous operation completed.") def sync_blocking_example(): import time time.sleep(1) # 此处展示的就是同步阻塞的情形 print("Blocking synchronous operation finished.") sync_nonblocking_example = lambda : None if not input('Press Enter to continue...') else True #模拟简单的非阻塞同步逻辑 ``` 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值