幸运的兔脚

2018年12月13日

同步、异步与回调

这段时间又开始看书学习了,所以也顺带的温故了一下以前学习的概念知识,在看到同步、异步与回调相关时,那真是一脸懵逼,很显然,忘得一干二净,果然知识还是得用,不用就得记下来啊。

同步 & 回调 & 异步

首先要说明,同步、回调和异步的概念要和多线程的概念区分开来,同步、回调和异步其实指的是调用方式,虽然他们和多线程确实是有不可区分的关系,但它们确实是两个不同的概念。

多线程

既然说到了多线程,那么就稍微记上几笔。

多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。
在程序中使用多线程技术,可以同时处理多个任务,达到提升效率/优化体验等目的。

同步调用

同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用。依据微软的 MSDN 上的解说:

同步函数:当一个函数是同步执行时,那么当该函数被调用时不会立即返回,直到该函数所要做的事情全都做完了才返回。

也就是说,当一个线程调用一个同步函数时,如果该函数没有立即完成规定的操作,则该操作会导致该调用线程的挂起(将 CPU 的使用权交给系统,让系统分配给其他线程使用),直到该同步函数规定的操作完成才返回,最终才能导致该调用线程被重新调度。

简单讲就是:调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。

//同步加法
function Add(a, b) {
  return a + b
}
Add(1, 2)

output: 3

异步调用

异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。

这样解释也许有些难懂,因为上面这个解释也涉及了一部分回调内容,换个简单说法就是:

异步调用类似一种消息传递,在调用异步函数后,函数会立刻返回,而异步调用通常可以在另外一个线程中或者等待收到主线程消息后进行执行,整个过程,不会阻碍调用者的工作。

对于“不会阻碍调用者的工作”这一点微软的 MSDN 上也有解说:

异步函数:如果一个异步函数被调用时,该函数会立即返回尽管该函数规定的操作任务还没有完成。

//异步加法
function LazyAdd(a) {
  return function(b) {
    return a + b
  }
}
var result = LazyAdd(1) // result等于一个匿名函数,实际是闭包
//我们的目的是做一个加法,result中保存了加法的一部分,即第一个参数和之后的运算规则,
//通过返回一个持有外层参数a的匿名函数构成的闭包保存至变量result中,这部是异步的关键。
//极端的情况var result = LazyAdd(1)(2);这种极端情况又不属于异步了,它和同步没有区别。

// 现在可以写一些别的代码了
console.log('wait some time, doing some fun')
// 实际生产中不会这么简单,它可能在等待一些条件成立,再去执行另外一半。

result = result(2)

output: 3

回调

回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口。回调和异步调用的关系非常紧密,通常我们使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。

同步回调

也许是因为基于回调定义的理解,很多人在分析回调时,会说到同步回调,所谓同步回调就是一个单纯的函数调用,最终还是一种线性执行过程。

//Foo函数意在接收两个参数,任意类型a,和函数类型cb,在结尾要调用cb()
function Foo(a, cb) {
  console.log(a)
  // do something else
  // Maybe get some parameters for cb
  var param = Math.random()
  cb(param)
}
//定义一个叫CallBack的函数,将作为参数传给Foo
var CallBack = function(num) {
  console.log(num)
}
//调用Foo
Foo(2, CallBack)

在上面这个例子中,函数 Foo 调用了函数 callback,但是整个过程还是同步的,其实就是一种变相的同步调用,所以对此,不再进行多余的讲解。

异步回调

异步回调就是我们常说的回调了,就如前面说的,回调和异步调用的关系非常紧密,具体先看代码:

//注意还是前面那个Add
function Add(a, b) {
  return a + b
}
//LazyAdd改变了,多了一个参数cb
function LazyAdd(a, cb) {
  return function(b) {
    cb(a, b)
  }
}
//将Add传给形参cb
var result = LazyAdd(1, Add)
// doing something else
result = result(2)

output: 3

乍一看,LazyAdd 函数的结构和异步时的结构基本差不多。不过这里的 LazyAdd 多了一个 cb 参数,那么这里的 cb 和同步回调时的 cb 有什么区别呢?其实 cb 都是一样的,只是在使用时,前者的 LazyAdd 在调用时为内部参数 a 添加了一个值,也就保存了一个状态在 result 内部,而只有需要带状态的才叫回调函数

分析

总的来说,同步和异步还是很好理解的,而回调的理解多少有些难度,在理解上,首先要明确异步是异步,回调是回调他们并没有太多的概念之间关系,只是在用的时候,异步和回调经常一同出现。

其他

本次简单记录了一下同步、回调和异步之间的关系和现象,用了最近正好在学的 js 举了几个例子,其实不管什么语言,对于思路上来说应该时差不多的,但是根据语言机制,可能在实现和原理上会有不同,如果有机会,还是要去学习一下语言机制。

参考:
详解回调函数——以 JS 为例解读异步、回调和 EventLoop
异步和多线程有什么区别
关于 js 的回调、同步回调、异步回调
异步和多线程有什么区别
同步(Synchronous)和异步(Asynchronous)
同步与异步,回调与协程
同步函数与异步函数