同步和异步:
面试问题什么是异步非阻塞
A. 同步
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
B. 异步
异步的概念和同步相对。
当一个异步过程调用发出后,先返回,调用者不会立刻得到结果。
例如 js中的setTimeout函数,是个异步过程,先返回。
实际处理这个调用的部件是在调用发出后,
通过状态、通知来通知调用者,或通过回调函数处理这个调用。
以 Socket为例,setblocking(False) 之后,当一个客户端通过调用 Connect函数发出一个连接请求后,调用者线程不用等待结果,可立刻继续向下运行。
当连接真正建立起来以后,socket底层会发送一个消息通知该对象。
在这个语义环境下,阻塞与非阻塞,是指请求的受理者在处理某个请求的状态,如果在处理这个请求的时候不能做其它事情(请求处理时间不确定),那么称之为阻塞,否则为非阻塞。
回调:
1、回调是什么:
知乎:回调函数(callback)是什么?
但是有些库函数(library function)比如qsort,却要求应用先传给它一个函数(判断两个字符的大小的方法),好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。
需要的额外信息
一个函数B将自身通过反射的方法传递给另一个函数A,A在某个时间调用B的方法,就叫回调。
必须要有“先传一个函数作为参数”的动作,否则就是系统调用
def main() -> 主调函数
A(B,4)
def A(B,x) -> 中间函数
B(x)
stat1
def B(x) -> 回调函数
这是一个同步回调(阻塞式回调),A中先执行B,再执行stat1
这里要注意:事件驱动机制的回调是异步的,非阻塞式的。
function f2() { //回调函数
console.log('f2 finished')
}
function f1(callback) { //中间函数
setTimeout(callback,1000) //用setTimeout()模拟耗时操作
console.log('f1 finished') //stat1
}
f1(f2); //得到的结果是 f1 finished ,f2 finished
这里是一个异步回调,先执行stat1,再执行f2
var fs = require("fs");
cb = function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
}
fs.readFile('input.txt','utf-8', cb);
console.log("程序执行结束!");
fs.readFile是一个异步函数,回调函数是cb,中间函数是fs.readFile(单语句作为一个函数)
假如在这个cb中继续调用了异步处理函数,就形成了一个回调函数链
这里可以看到,异步回调的前提是先需要一个异步函数,通常我们用的函数都属于同步函数,而异步函数用独特的方法来实现:
function asyncFunction(a){
return True;
do sth. other...
}
这里有个问题是异步函数要如何将它的执行结果返回给上级函数
方法有很多,比如我们可以使用一个控制器去监听一个url上的网络请求,当asyncFunction执行完毕的时候对该url发送消息(那么此时控制器里的处理方法就是异步回调函数了)。
或者在更一般的网络请求中,我们发出一个GET或者POST的异步请求,设置select/epoll进行监听。当网络数据到来时调用函数处理。
selector.register(conn, selectors.EVENT_READ, callback) #callback函数就是回调函数
#这里的conn是异步的
这就是异步回调:在发起一个异步任务(等待来自conn的数据,函数先返回)的同时指定一个函数,在异步任务完成时会自动的调用这个函数。
3、从网上截取的一些关于回调的说法,可以帮助理解:
我们常说的同步回调,指的就是一个代码执行过程中,需要等到回调函数完全执行完后,才能往 下走。
异步回调指的是一个代码执行到回调函数时,他可以先返回,就能往下走。
而异步回调和同步回调最大的不同就是异步回调里新建了一个子线程。异步回调常见于请求服务器数据,当取到数据时,会进行回调。
回调模型会加大我们的编程负担。
事件驱动模型:
事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。事件驱动是设计模式中观察者模式的一个实现。
不用回调函数,也可以实现事件驱动。例如:把事件消息发送到队列,另外一个进程取队列处理即可(没有回调函数)。
这种模型可以解耦事件发送者和接收者之间的联系
事件驱动模型大体思路如下:
- 有一个事件(消息)队列;
- 鼠标按下时,往这个队列中增加一个点击事件(消息);
- 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等;
- 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
事件驱动实现的形式可以是两种:1.事件发生时线程调用对应的回调函数。2.在中断向量上注册事件,事件发生时执行中断程序。
事件驱动使用的线程个数:可以是单线程如Twisted,双线程如Netty。双线程的事件驱动模式,可以自然地异步回调来实现,Netty的运行原理是个典型的双线程事件驱动模型,用一个线程处理所有的连接,这个线程通常是一个循环的方法,当处理一个连接遇到阻塞的操作就将任务丢给其它的线程,主线程接着处理下一个连接。
协程:
协程可以使用同步的方式写异步代码,通过库或者语言的调度来实现并发。协程与回调对比,优势一目了然:代码更清晰直观,更加符合思维习惯。
协程是对异步回调链的封装
Twisted:
但因为twisted属于单线程异步回调,所以在回调函数中的阻塞发生时reactor线程也会阻塞。
同步版本返回的是诗歌内容,而异步版本返回的却是一个deferred。
当然,这个函数是不能随意激活这个deferred的,因为它已经返回了。但这个函数已经启动了一系列事件,这些事件最终将会激活这个deferred。
一个deferred已经绑定了一系列的处理事件。当整个socket数据接收完毕后,就开始进行整个事件的处理。
[外链图片转存失败(img-WKL2CwZC-1562683562246)(http://wiki.jikexueyuan.com/project/twisted-intro/images/p01_async.png)]
那张图显示的是异步编程时的时间片使用。在程序上是表现为三个套接字abc,a就绪,b就绪,c就绪,然后reactor run 后abc同时开始执行(这是异步,因为abc均已返回但并未得到最后的返回结果)。a接收完所有数据之后,开始执行注册好的Deferred,这也是异步,因为deferred在之前已经返回。显然,如果abc使用异步,那么处理过程通过回调函数而不是轮询来执行是一个很自然的行为。这就导致了Deferred也会是一个异步过程。
OS线程中的阻塞和阻塞I/O都提到了阻塞
阻塞(block)线程的意思是该线程自己阻塞自己,把CPU让出来,自己停止执行。
当一个线程执行阻塞IO的时候,它就阻塞自己执行其他线程。但是如果是类似python这样的单线程程序呢,线程阻塞了,那就只能空等了(其实是会执行其他进程)。所以出现了Twisted,可以让单线程阻塞程序变为非阻塞(相对意义上的,这里只是epoll,网络IO是非阻塞的,其他IO还是阻塞的)。
阻塞和顺序执行:如果是阻塞的,就会顺序执行,非阻塞程序不会顺序执行,比如先访问A,再访问B,如果B的访问时间较短,epoll模型会先执行B的结果。
所以阻塞和单或多线程其实也没有必然的关系。
附一个很精彩的文章,详细解释了epoll。还有个答案讲的网卡驱动,和本文无关但是很精彩。。以前想过写linux驱动来着。
https://www.zhihu.com/question/20122137
有一些新的想法,可能大部分的软件架构就是人的活动的一种抽象。比如事件驱动,就可以从人的日常活动中提炼出来,设想一个礼拜天你在家里打游戏/上网,这个时候你妈妈打电话过来说外面下雨了,让你去收衣服,这就是一个典型的事件驱动,你这个时候去收衣服,就是执行了回调函数,如果不去收继续打游戏,就是出了BUG。。硬件层面的事件驱动体现在中断,当有事件到来时去处理,比如网卡接受到包,引起一个中断,将包放入tcp协议栈处理。而软件层面的事件驱动就体现在异步回调。
至于协程,不知道大家小时候有没有参加过数学竞赛,做过一类题规划时间的题:
烧一壶水要8分钟,灌开水要1分钟,取牛奶和报纸要5分钟,整理书包要6分钟,为了尽快做完这些事,怎样安排才能使时间最少?最少需要几分钟?
这类问题都属于统筹问题