同步~异步~阻塞~非阻塞

原文地址:同 步 和 异 步作者:zenos

 

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

    同步(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的执行速度和效率!异步调用和非阻塞调用是站在整个系统的层面上考虑问题的,它们的目的在于使得整个系统运行的性能效率提高,各个部件之间运行配合更加协调。这才是它们真正的优点。所以我们在想把某个模块从同步/阻塞方式改为异步/非阻塞方式的时候就要考虑是否真的能够达到预定的目的。

 

上一篇:SpringBoot_pom_02_打包


下一篇:线程池和任务