[转]异步机制与异步原理

前言

昨天在总结javascript异步编程的时候,提到了promise和目前比较流行的async模块,不过,在比较这两个解决方案的时候,在我个人的认知上感觉两个没有什么太大的本质区别,于是去请教了一些前辈来解答两个不同方案的优劣,在解答的过程中,涉及到了对一些异步原理的部分。

然后,感觉自己整个人的三观都被刷新了。

在了解了一些原理相关的内容之后,发现自己所理解的异步太浮于表面,这和网上看到的一些人的一些技术文章有很大的关系,关于网上发布的一些所谓的技术文章,大多都没有什么权威性,大部分都是个人在某个阶段中所理解的内容,而我对异步的理解也被大量的这类文章所影响,并且深信不疑。

为了最终去了结异步到底是怎样的一个过程,以及异步所产生的初衷,我去寻找了一些比较具有权威性质的文章,然后也请教了一些对于这方面研究比较深入的前辈。

浅谈异步

对于异步,不知道是从哪里第一次接触到的这个词语,我第一次接触到异步,是源自jquery的ajax,jquery对于ajax请求开放了两种方式,一种是同步,一种是异步,而我第一次对于异步的了解就是通过ajax的异步请求。

现在,网上大多数对于异步模式产生原因的解释大多都是这样的:因为javascript的执行环境为单线程,所以程序在执行的时候只能按照顺序执行,而其中一个程序发生太大的资源消耗,会导致后面的程序过长的等待,造成阻塞的情况。

其实,乍一看,这样的解释似乎也没有什么问题,因为在javascript的编程体验上,这样的解释是比较符合开发感受的,久而久之,很多人对于异步的理解就和我一样变成了,为了解决两个需要同时执行的任务这样的理解。

不过,其实将这个想法往深追究其实是站不住脚的,因为不管是采用了怎样的机制,javascript所运行的环境已然是单线程,这是不可改变的事实(worker模块除外),这也就导致了不管表面上是怎样的,实际内部两个任务是不可能同时执行的,在任务队列中,不管怎样都是会有一个执行顺序的。

而也正是因为这样,异步其实并不能本质上解决程序阻塞的问题。

举个例子:

function bendan (callback) {
    callback();
}
bendan(() => {
    console.log(1);
    for(var i = 0; i < 100000000; i++) {
        console.log(2);
    }
});
console.log(3);

上面这段代码是一个比较好理解的异步过程,如果按照错误的异步理解方式,那么这个程序运行的结果应该是可以将3顺利的打出,甚至应该在打印2这个比较耗费资源的过程之前就打印出3,但是,实际上的结果是,这段程序在打印2的时候就发生了阻塞,3并不能顺利的打印出来。

这就是异步和多线程最本质的区别,也就此说明了,异步本质上其实解决不来资源消耗所发生的阻塞问题。

异步原理

那么,异步到底是为了解决什么样的问题的呢?或者说他究竟是怎样的情况下被发明出来的呢?

这要从javascript的特性以及发展说起,javascript这个语言在发明的过程中,其实考虑的并不是很完善,因此有很多地方和最初设想的不太一样,比较典型的例子就是闭包,闭包的产生其实最初只是因为javascript垃圾回收机制的一个缺陷所产生的一个问题,但是却意外地变成了一个彩蛋,类似这样的有很多,只不过没有其他作用的变成了坑,而可以在其他方面有发展的则变成了彩蛋。

异步其实就是如此。

javascript有一个其他语言所不太具备的方便设计,回调,当然,其他的语言现在也可以实现回调,只不过回调的实现成本比较高,就比如java之前不支持回调,如果想要实现回调的相关效果,需要定义个interface,a去赋值,b函数接收他,这个回调函数还是个局部变量,a改了,b里的也改了,然后b里再去执行,这样很繁琐。

但是javascript不一样,javascript在设计中本身就对回调进行了支持,而在往下看,回调产生了一个很有趣的东西,那就是闭包,而闭包把自身的作用域暴露给了其它对象,什么意思呢?就是说它自身也不知道什么时候会被执行,所以产生了异步,如果这个function,用了函数局部变量,那传到外面后执行也能拿到,为什么,因为作用域出去了,就是闭包,所以promise,async是为了解决这种问题,让回调函数被执行完了,再往下执行。

也就是说,异步其实在最初只是为了使用回调所产生了一个结果。

那么为什么异步就变成了彩蛋,然后现在被打大量的使用呢?还有,异步的具体使用场景究竟是什么呢?

这要从程序运行的资源消耗说起,首先要明确一个概念,运行时间长并不代表所消耗的资源多,举个简单的例子,ajax请求,在这样的请求中会有较大的时间开销,但是这一过程中并没有太大的资源消耗,或者说对于javascript所执行的环境来说几乎没有什么资源的消耗。

所以在这样的过程中,其实主线程是空闲着的,如果不是异步的行为,那它所做的只有一件事,那就是等待,等待请求的响应,那么,其实在这样的一个过程中,完全可以把主线程在等待时候的资源开放出来进行其他的操作,这就形成了异步。

虽然这样的一种行为,和多线程看似一样,因为都是在前面的程序没有执行完的时候就开始执行了下面的程序,但是实际上却是有着本质的区别,如果不是像这种等待的操作,而是运算量比较大的那种,单线程会卡住,而这种情况只有多线程能解决,异步不能。

然后,javascript是怎样做到利用这个等待资源的呢,这和javascript的运行机制有关,它会把需要执行的放在一个执行队列中,像是这种需要等待的程序,他在会在执行了之后将他排在后面,然后执行其他的任务,指导它等待结束之后,执行它的回调。

对于这样一个过程的具体实现,要引入一个叫做帧的概念,对于同步代码是在当前帧运行的,异步代码是在下一帧运行的。

盗用一个别人的讲解图。

这样就能简单的理解这个运行过程。

总结

首先,最大的感触就是,对于网络上的一些技术文章要有判断能力,虽然说这是可以获取很多新技术的最有效的途径,但是很多技术文章也都是个人的见解,他们说出的一些不具有权威性,也不需要负任何的责任,其次,最好还是多看书,大部分可以出版的内容还是具有一些权威性的,至少要比网上的要好。

其实对于异步的理解对于业务相关影响不是特别大,但是像是这样的理解偏差可能会造成一些不可预计的坑,所以能了解本质的话还是要尽量的追溯到事情的本质。

然后,现在nodejs已经可以进行多线程的编写,感兴趣的可以去找一些worker。

最后,对于这个的理解之后,我明白了promise和async的区别,async所解决的是实现了同步的写法执行异步操作,更清晰易懂的通过编写同步代码实现异步操作,对于async的理解可以再稍微看看generator,应该会有更深的理解。

 

 

转自 wanglanye

posted @ 2017-11-29 20:43  Sameen  阅读(6962)  评论(2编辑  收藏  举报