异步的另外一种含义是计算机多线程的异步处理。与同步处理相对,异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。异步与多线程与并行不是同一个概念。
先问自己几个问题
- javascript是单线程的吗?
- javascript如果是单线程为什么能让ajax异步发送和回调请求?
- setTimeout一定是按照它定的时间发生的吗?
- 浏览器是多线程的吗?
- event loop是什么呢?
- 下面这段代码的结果是什么?
1 | function foo(){ |
javascript是单线程
方便理解,可以这么想,如果js不是单线程的,那么当我们要可能在操作一个DOM的时候(比如修改,删除,添加),另一个线程可能也在对这个DOM节点进行操作。这么肯定是不行的。
这跟javascript最初的用途是有关的,js本来设计就是为了更好交互效果,更方便操作DOM,这一特点某种程度上决定了他的单线程。
js运行在浏览器中是单线程的。每一个window一个js线程。
由于是单线程,那么在一个时刻只能够执行某个特定的代码,并且阻塞其他的代码。这也就意味着如果前面一个任务耗时很长的时候,后一个任务就会一直等待。
js的单线程机制其实是:有两个队列,一个是主线程。一个是任务队列。
浏览器不是单线程的
js运行在浏览器中,是单线程的,每个window一个js线程,但是浏览器不是单线程的。他可能有很多线程,比如js引擎线程,界面渲染线程,浏览器事件触发线程,http请求线程。
单线程的机制
刚刚提到了javascript的单线程的机制其实是有两个队列,一个是主线程队列,一个是任务队列。
之所以要有这么两个队列:
JavaScript语言的设计者意识到,这时主线程完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去。
于是,所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。–from ruanyifeng
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
js都是先执行同步任务,再执行异步任务。
那么上面的第六个问题就很清楚了,答案是先输出10个outer,再输出10个inner。因为outer是位于主线程上,inner是位于任务队列中,js在执行的时候必须先把主线程的做完,再执行任务队列。在主线程和任务队列这两个队列里又是又是阻塞的。
1 | setTimeout( function(){ while(true){} } , 100); |
上面这个的结果里是可以输出hello的,跟我们想的一样,但是alert的你好,是永远都没有办法出来的。原因上面说了。
异步事件驱动
浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,例如:鼠标点击事件、窗口大小拖拉事件、定时器触发事件、XMLHttpRequest完成回调等。当一个异步事件发生的时候,它就进入事件队列。浏览器有一个内部大消息循环,Event Loop(事件循环),会轮询大的事件队列并处理事件。例如,浏览器当前正在忙于处理onclick事件,这时另外一个事件发生了(如:window onSize),这个异步事件就被放入事件队列等待处理,只有前面的处理完毕了,空闲了才会执行这个事件。setTimeout也是一样,当调用的时候,js引擎会启动定时器timer,大约xxms以后执行xxx,当定时器时间到,就把该事件放到主事件队列等待处理(浏览器不忙的时候才会真正执行)。
setTimeout(func, 0)一定是0s就执行吗?
上面说了必须要等主线程的所有事件被完成时,才能够执行任务队列上的事件。
setTimeout(func, 0)这句话,告诉了js引擎,要在0s之后吧func函数放到主事件队列里面,等待当前其他的所有的主线程任务完成了,就开始执行。之所以有用,是因为当前并没有主线程上并没有其他的任务。如果当前的主线程上有其他的任务,那么其实func的执行时间,绝对不会是0s。因为它还要排队。
总结异步执行的运行机制
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
总的来说就是,任务队列里的任务都是必须等主线程的执行栈空了才去执行的。我们通常说的同步也就是没有上面的异步任务。
Event Loop是啥子?
主线程从“任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
为什么会看见下面这种现象?
现在想想以前写xhr的时候就在书上这么看过,但是当时也没多想,虽然知道是异步没关系。1
2
3
4
5
6
7
8
9
10
11
12//写法一
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function (){};
req.onerror = function (){};
req.send();
//写法二
var req = new XMLHttpRequest();
req.open('GET', url);
req.send();
req.onload = function (){};
req.onerror = function (){};
这两种写法是一样的,为什么呢?因为send是一个异步执行过程,那么他会等当前所有的非异步执行完再去执行。它被放入了任务队列。
再来看看异步,多线程,并行的概念
并行,一般指并行计算,是说同一时刻(注意并发是同一时间段)有多条指令同时被执行,这些指令可能执行于同一CPU的多核上,或者多个CPU上,或者多个物理主机甚至多个网络中.
异步,与同步相对应,异步指的是让CPU暂时搁置当前请求的响应,处理下一个请求,当通过轮询或其他方式得到回调通知后,开始运行。
线程,是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
总结
了解了js单线程,知道什么是异步事件驱动,知道了有主线程的执行栈和任务队列这两个机制。还有setTimout不一定就是在它设置的时间执行呦。学习和整理自ruanyifeng的文章,感谢。