“关于‘回调’,99%的人不知道!”、“关于回调看这一篇就好了!”
言归正传,关于回调函数,前端后端移动端三端应该都很熟悉了,例如移动端Android开发里的点击事件:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showToast();
}
});
再简单不过的一个回调了,给button设置点击事件,当点击事件发生时,显示一个吐司消息。lamda表达式的写法如下:
button.setOnClickListener((view)->{showToast();});
我们都知道,一般情况下我们写好的代码都有个主(main)函数,作为程序的入口,代码是顺序执行的(你可能会觉得奇怪,Android开发里好像没有见过main函数,见得多的是framework暴露出来的生命周期的函数,onCreate()之类的;但是实际上Android里面肯定也是有main函数的,只不过没有暴露出来,由系统管理)。
代码顺序执行到这里的时候,里面的showToast并没有被执行,而只是被注册了回调,在将来某个合适的时候被执行;也就是当点击事件发生的时候,由系统进行调用。
如果我是一个计算机小白,想不用回调函数实现点击事件触发的效果,我只能想到写个死循环去轮询button的状态,当button处于被点击状态时,再调用showToast()。
这个过程,就像二狗晚上每隔三分钟打开微信,检查女神有没有给他发消息;我们知道轮询的开销是很大的,而且还容易造成阻塞,导致应用失去响应;所以二狗身心俱疲,除了等消息啥也做不了,觉都睡不好。
后来二狗换了个带通知功能的手机,而且女神的消息还设置了特别提示音,这样二狗就不用担心错过女神的消息了。这里手机的主动通知就相当于注册的回调函数被调用了,女神的特别提示音就相当于回调的时候传入的实参。二狗的大脑接受了提示音,做了一点判断,得出这是女神的消息,然后开始了舔狗的基操
回到本文开始的问题,我们都知道回调函数的调用是由系统进行的,那系统是怎么实现的呢?系统,比如我们的手机,能响应一秒内的一亿次点击事件吗?
答案:其实底层来说,回调的本质就是“轮询”!一亿次理论上是可以响应的!
计算机其实没有啥魔法,也没有啥秘密,很多复杂的功能其实是非常简单的实现。
CPU在每个指令周期,在执行完指令之后,会去查看中断寄存器的状态,如果有中断,那么就跳转到中断处理程序。(参看《Operating System Concepts》的17章)
追本溯源,回调本质上是CPU的中断触发的,而中断的本质原理就是cpu去做“轮询”:在每个指令周期去检查中断寄存器的状态,效率比我们一般说的软件层面的轮询高很多。(触摸屏幕产生电信号对于中断寄存器的状态的改变过程这里就不做研究了)
那这个轮询效率有多高呢?我们知道CPU的一个重要组成部分是同步时钟信号,就是我们所说的主频,一个指令周期由若干时钟周期构成。
如果按照图上的一个指令周期由10个时钟周期(实际上很多指令周期没有这么长)来计算,主频为2.0GHz的CPU就是一秒钟内有2*10的9次方也就是20亿次的时钟周期,折合2亿个指令周期;相当于一秒钟内轮询了两亿次寄存器的状态。
所以我们开头提出的一亿次点击理论上响应是没问题的,但是根据我们的直觉,这肯定是不现实的,所以我们说是理论上可行。因为还存在其他很多因素掣肘,触摸屏幕的传感器不能产生对应的这么高频率的电信号;以及CPU硬件的上层,软件层面的操作系统等的一系列的过程效率肯定是更低的,无法短时间处理这么大量的点击事件。其实同理可知,网络请求的回调也是同样的过程。我们经常使用网络框架,也一般是传入两个回调函数onSucess(),onFailed();网络端口的监听,底层也是中断的处理;然后根据报文里携带的信息,系统决定调用onSucess()还是onFailed()函数。
水平有限,文中如有错误还请各位指出,我是梦龙Dragon