javasciprt的单线程及异步实现浅析

问题的引起

众知周知,javascript引擎运行是单线模式的,就是同一个时间里面只能有一段代码运行。

问题一:javascript是如何实现异步的呢?这里的异步和我们常用的java、c#语言的原理是否一致的呢?

问题二:下面的代码执行顺序是怎样的?setTimeout中的第二个参数为0,直观上认为是马上执行,那是不是表示直接调用一个方法一样呢?
setTimeout(function(){
    alert("I'm in setTimeout");
},0);
alert("I'm not in setTimeout");
问题三:下面的两段代码,分别的执行情况是怎样的呢?
setTimeout(function(){alert("setTimeout1);while(true){};},1000);
setTimeout(function(){alert("setTimeout2);},2000);
setTimeout(function(){alert("setTimeout3);},3000);
alert("over");
var flag = true;
setTimeout(function(){ flag = false; }, 1000);
while(flag){ }
alert("end");
假如你都能很快地回答出上面的问题,那恭喜你,你对javascript的单线程执行处理应该是有一定的了解的了。如果还不能很快地回答出来,那么相信你看了下面的解析后,也能很好地回答了。


问题的陈述

要弄清楚上面的问题,先要弄清楚浏览器内核处理定时器(setTimeout、setInterval)和响应浏览器的事件与javascript引擎之间的关系问题。

目前流行的浏览器内核有:IE内核(Trident),Chrome和Safari的内核(Webkit),Firefox内核(Gecko),Opera内核(Presto),这些内核在与javascript引擎通信的实现细节上可能会有所不同,但基本的原理是一致的。
浏览器内核允许有许多个线程异步执行的,假设浏览器现在有至少三个的常驻线程:javascript引擎线程、界面渲染线程、浏览器事件触发线程。有一些非常驻线程即执行完就结束的线程例如Http请求线程等,上述的这些线程都会产生不同的”异步“事件。借用网上的图来表示
javasciprt的单线程及异步实现浅析_第1张图片
定时器的计数并不是由javascript引擎来计算的,假如是javascript引擎计算,因为是单线程的关系,那么javascript引擎就处于阻塞状态就一直在处理计数,不做别的操作。但我们从现象上可以看出,调用setTimeout后,是不会阻塞下一段代码的执行的,因此定时器的计数就肯定不是javascript引擎计算的了,那就只能是浏览器来做这个工作了。当定时器到达要执行时,就把任务插入到javascript引擎中,所以我们看到的现象就是异步的,但这里的异步不是指javascript引擎的异步。
同样的,假如浏览器捕捉到了一个点击事件,浏览器将此事件放到javascript引擎的队列中,点击事件就会排队被执行。
另外,浏览器的界面渲染线程与javascript引擎执行线程是互斥的。因为javascript脚本可以对Dom进行操作,如果一边脚本在操作的同时,界面渲染线程在工作,就会导致前后的数据不一致,javascript脚本操作的结果与界面看到的不一样。所以在javascript引擎运行时,浏览器的渲染线程会被挂起。

上面的陈述会比较容易理解,我在理解时参考了以下的文章:
http://julying.com/blog/javascript-settimeout-thread/
http://www.cnblogs.com/sprying/archive/2013/05/26/3100639.html


问题的解答

看了上面的问题陈述后,你是否能得到上面问题的答案了呢?让我们来看一下。


问题一:javascript引擎执行是单线程的,可以实现异步是由于与浏览器内核中的线程相互配合完成的,由浏览器去完成一些如定时器的计数,当达到需要时间需要执行时,把任务插入到javascript引擎的线程中排队等待被执行,这里有个问题,假如当前有其它的任务执行时间较长,那么上述插入的任务会补延后,因此setTimeout函数的第二个时间参数time,实际会是等于或大于time执行,即假如是10s,有可能刚好10s执行,有可能是11s执行,不可能是9s执行。

java或c#这些语言中,是真正的支持多线程的,与javascript有本质的不同。


问题二:执行顺序会是"I'm not in setTimeout",再到"I'm in setTimeout",为什么?setTimeout中第二个参数是0,为什么不是马上执行?上面的问题陈述说了,定时器是由浏览器去计数的。javascript引擎的线程在执行这段代码时,setTimeout函数首先被调用,然后扔给浏览器的线程去定时计算,不得结果的返回,javascript引擎的线程马上执行下一段代码,即alert("I'm not in setTimeout");当浏览器的线程计算是0,需要执行时,把里面需要执行的代码插入到javascript引擎的线程中进行排队,然后javascript引擎的线程就会执行这个任务,输出"I'm in setTimeout"。

因此,使用了setTimeout(function(){},0)与直接执行的区别是,setTimeout会扔到浏览器中,再插入到javascript引擎线程中,然直接执行就是javascrtip引擎线程直接执行了。关于应用场景,这里有个比较直观的例子。


问题三:第一段代码,输出是"over",再到"setTimeout1",最后javascript引擎不会再响应和执行其它任务,因为javascript引擎是单线程的,到执行alert("setTimeout1")后,一直在执行while(true){};

第二段代码同样的道理,setTimeout会被先放到浏览器去计数,然后执行while(flag){};之后javascript引擎一直在执行这段代码,即使后面的浏览器计算到期把任务插入到队列中,也不会被javascript引擎执行,因为它是单线程,因此flag就不会被设置为false,所以"end"也不会被输出,这段代码的执行结果就是浏览器像”卡死了“,不会有任何输出。


总结

1、javascript引擎只有一个线程,异步事件是由于有浏览器的配合得以完成,浏览器往javascript引擎的线程队列插入任务,与java中的多线程有本质区别;

2、如果一个计时器被阻塞执行,例如当前执行的其它任务耗时>定时的时间,它被执行的时间点会比预期更长。


本人是初学者,上面的描述可能有不够严谨或者是错误的地方,请大家批评指出。


你可能感兴趣的:(javaScript)