JavaScript是否可实现多线程  深入理解JavaScript定时机制


Posted in Javascript onDecember 22, 2009

容易欺骗别人感情的JavaScript定时器

JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如

setTimeout( function(){ alert('你好!'); } , 0); 
setInterval( callbackFunction , 100);

认为setTimeout中的问候方法会立即被执行,因为这并不是凭空而说,而是JavaScript API文档明确定义第二个参数意义为隔多少毫秒后,回调方法就会被执行. 这里设成0毫秒,理所当然就立即被执行了.
同理对setInterval的callbackFunction方法每间隔100毫秒就立即被执行深信不疑!

但随着JavaScript应用开发经验不断的增加和丰富,有一天你发现了一段怪异的代码而百思不得其解:

div.onclick = function(){ 
setTimeout( function(){document.getElementById('inputField').focus();}, 0); 
};

既然是0毫秒后执行,那么还用setTimeout干什么, 此刻, 坚定的信念已开始动摇.

直到最后某一天 , 你不小心写了一段糟糕的代码:

setTimeout( function(){ while(true){} } , 100); 
setTimeout( function(){ alert('你好!'); } , 200); 
setInterval( callbackFunction , 200);

第一行代码进入了死循环,但不久你就会发现,第二,第三行并不是预料中的事情,alert问候未见出现,callbacKFunction也杳无音讯!

这时你彻底迷惘了,这种情景是难以接受的,因为改变长久以来既定的认知去接受新思想的过程是痛苦的,但情事实摆在眼前,对JavaScript真理的探求并不会因为痛苦而停止,下面让我们来展开JavaScript线程和定时器探索之旅!

拔开云雾见月明

出现上面所有误区的最主要一个原因是:潜意识中认为,JavaScript引擎有多个线程在执行,JavaScript的定时器回调函数是异步执行的.

而事实上的,JavaScript使用了障眼法,在多数时候骗过了我们的眼睛,这里背光得澄清一个事实:

JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序.
JavaScript引擎用单线程运行也是有意义的,单线程不必理会线程同步这些复杂的问题,问题得到简化.

那么单线程的JavaScript引擎是怎么配合浏览器内核处理这些定时器和响应浏览器事件的呢?
下面结合浏览器内核处理方式简单说明.

浏览器内核实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步.假如某一浏览器内核的实现至少有三个常驻线程:javascript引擎线程,界面渲染线程,浏览器事件触发线程,除些以外,也有一些执行完就终止的线程,如Http请求线程,这些异步线程都会产生不同的异步事件,下面通过一个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通信的.虽然每个浏览器内核实现细节不同,但这其中的调用原理都是大同小异.
JavaScript是否可实现多线程  深入理解JavaScript定时机制
由图可看出,浏览器中的JavaScript引擎是基于事件驱动的,这里的事件可看作是浏览器派给它的各种任务,这些任务可以源自 JavaScript引擎当前执行的代码块,如调用setTimeout添加一个任务,也可来自浏览器内核的其它线程,如界面元素鼠标点击事件,定时触发器时间到达通知,异步请求状态变更通知等.从代码角度看来任务实体就是各种回调函数,JavaScript引擎一直等待着任务队列中任务的到来.由于单线程关系,这些任务得进行排队,一个接着一个被引擎处理.

上图t1-t2..tn表示不同的时间点,tn下面对应的小方块代表该时间点的任务,假设现在是t1时刻,引擎运行在t1对应的任务方块代码内,在这个时间点内,我们来描述一下浏览器内核其它线程的状态.

t1时刻:

GUI渲染线程:

该线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行.本文虽然重点解释JavaScript定时机制,但这时有必要说说渲染线程,因为该线程与JavaScript引擎线程是互斥的,这容易理解,因为 JavaScript脚本是可操纵DOM元素,在修改这些元素属性同时渲染界面,那么渲染线程前后获得的元素数据就可能不一致了.

在JavaScript引擎运行脚本期间,浏览器渲染线程都是处于挂起状态的,也就是说被”冻结”了.
所以,在脚本中执行对界面进行更新操作,如添加结点,删除结点或改变结点的外观等更新并不会立即体现出来,这些操作将保存在一个队列中,待 JavaScript引擎空闲时才有机会渲染出来.或许这里你有个疑问了,为什么JS代码块的alert时界面有更新的,JS不是正在运行吗?其实当 alert发生时,浏览器内核就会挂起JavaScript引擎线程,并促使界面执行了更新.

GUI事件触发线程:

JavaScript脚本的执行不影响html元素事件的触发,在t1时间段内,首先是用户点击了一个鼠标键,点击被浏览器事件触发线程捕捉后形成一个鼠标点击事件,由图可知,对于JavaScript引擎线程来说,这事件是由其它线程异步传到任务队列尾的,由于引擎正在处理t1时的任务,这个鼠标点击事件正在等待处理.

定时触发线程:

注意这里的浏览器模型定时计数器并不是由JavaScript引擎计数的,因为JavaScript引擎是单线程的,如果处于阻塞线程状态就计不了时,它必须依赖外部来计时并触发定时,所以队列中的定时事件也是异步事件.

由图可知,在这t1的时间段内,继鼠标点击事件触发后,先前已设置的setTimeout定时也到达了,此刻对JavaScript引擎来说,定时触发线程产生了一个异步定时事件并放到任务队列中, 该事件被排到点击事件回调之后,等待处理.
同理, 还是在t1时间段内,接下来某个setInterval定时器也被添加了,由于是间隔定时,在t1段内连续被触发了两次,这两个事件被排到队尾等待处理.

可见,假如时间段t1非常长,远大于setInterval的定时间隔,那么定时触发线程就会源源不断的产生异步定时事件并放到任务队列尾而不管它们是否已被处理,但一旦t1和最先的定时事件前面的任务已处理完,这些排列中的定时事件就依次不间断的被执行,这是因为,对于JavaScript引擎来说,在处理队列中的各任务处理方式都是一样的,只是处理的次序不同而已.

t1过后,也就是说当前处理的任务已返回,JavaScript引擎会检查任务队列,发现当前队列非空,就取出t2下面对应的任务执行,其它时间依此类推,由此看来:

如果队列非空,引擎就从队列头取出一个任务,直到该任务处理完,即返回后引擎接着运行下一个任务,在任务没返回前队列中的其它任务是没法被执行的.
可以试试运行以下例子作测试,第一个定时器是一个死循环,第二个定时器是向界面输出一串字符串,第一个定时器比第二个定时器要来得早.

setTimeout(function(){ 
for(;;); 
}, 50); 
setTimeout(function(){alert('Hello');}, 51);

运行后可看出,在没浏览器没提示脚本繁忙前第二个定时器alert是不会弹出的,因为单线程关系,第一个定时器回调脚本未返回前第二个脚本是没机会运行.

相信您现在已经很清楚JavaScript是否可多线程,也了解理解JavaScript定时器运行机制了,下面我们来对一些案例进行分析:

案例1:setTimeout与setInterval

setTimeout(function(){ 
/* 代码块... */ 
setTimeout(arguments.callee, 10); 
}, 10); setInterval(function(){ 
/*代码块... */ 
}, 10);

这两段代码看一起效果一样,其实非也,第一段中回调函数内的setTimeout是JavaScript引擎执行后再设置新的setTimeout 定时, 假定上一个回调处理完到下一个回调开始处理为一个时间间隔,理论两个setTimeout回调执行时间间隔>=10ms .第二段自setInterval设置定时后,定时触发线程就会源源不断的每隔十秒产生异步定时事件并放到任务队列尾,理论上两个setInterval 回调执行时间间隔<=10.

案例2:ajax异步请求是否真的异步?

很多同学朋友搞不清楚,既然说JavaScript是单线程运行的,那么XMLHttpRequest在连接后是否真的异步?
其实请求确实是异步的,不过这请求是由浏览器新开一个线程请求(参见上图),当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理,当任务被处理时,JavaScript引擎始终是单线程运行回调函数,具体点即还是单线程运行 onreadystatechange所设置的函数.

Javascript 相关文章推荐
抽出www.templatemonster.com的鼠标悬停加载大图模板的代码
Jul 11 Javascript
javascript下function声明一些小结
Dec 28 Javascript
js给onclick事件赋值,动态传参数实例解说
Mar 28 Javascript
jquery自定义滚动条插件示例分享
Feb 21 Javascript
javascript实现表格排序 编辑 拖拽 缩放
Jan 02 Javascript
利用JavaScript的AngularJS库制作电子名片的方法
Jun 18 Javascript
JavaScript数组去重的几种方法效率测试
Oct 23 Javascript
利用js定义一个导航条菜单
Mar 14 Javascript
javascript 中关于array的常用方法详解
May 05 Javascript
vue中的inject学习教程
Apr 24 Javascript
JS中的算法与数据结构之链表(Linked-list)实例详解
Aug 20 Javascript
layui 上传插件 带预览 非自动上传功能的实例(非常实用)
Sep 23 Javascript
JavaScript 图片预览效果 推荐
Dec 22 #Javascript
javascript 年月日联动实现核心代码
Dec 21 #Javascript
Javascript和Ajax中文乱码吐血版解决方案
Dec 21 #Javascript
利用jQuery的$.event.fix函数统一浏览器event事件处理
Dec 21 #Javascript
jQuery 连续列表实现代码
Dec 21 #Javascript
jQuery解决iframe高度自适应代码
Dec 20 #Javascript
IE 上下滚动展示模仿Marquee机制
Dec 20 #Javascript
You might like
php批量上传的实现代码
2013/06/09 PHP
php打造智能化的柱状图程序,用于报表等
2015/06/19 PHP
PHP创建文件及写入数据(覆盖写入,追加写入)的方法详解
2019/02/15 PHP
jquery 实现两Select 标签项互调示例代码
2014/09/25 Javascript
Jquery实现textarea根据文本内容自适应高度
2015/04/03 Javascript
TypeScript具有的几个不同特质
2015/04/07 Javascript
Vue实现动态显示textarea剩余字数
2017/05/22 Javascript
ES6中箭头函数的定义与调用方式详解
2017/06/02 Javascript
原生JS实现图片懒加载(lazyload)实例
2017/06/13 Javascript
Windows下快速搭建NodeJS本地服务器的步骤
2017/08/09 NodeJs
浅谈JavaScript面向对象--继承
2019/03/20 Javascript
layui 解决富文本框form表单提交为空的问题
2019/10/26 Javascript
解决vue的touchStart事件及click事件冲突问题
2020/07/21 Javascript
[01:02:03]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS VG
2014/05/26 DOTA
[01:03:27]NAVI vs EG 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
[01:01:52]DOTA2-DPC中国联赛正赛 iG vs LBZS BO3 第一场 3月4日
2021/03/11 DOTA
Django 导出 Excel 代码的实例详解
2017/08/11 Python
pandas中的DataFrame按指定顺序输出所有列的方法
2018/04/10 Python
PyQt4实现下拉菜单可供选择并打印出来
2018/04/20 Python
python执行CMD指令,并获取返回的方法
2018/12/19 Python
使用PYTHON解析Wireshark的PCAP文件方法
2019/07/23 Python
DJANGO-URL反向解析REVERSE实例讲解
2019/10/25 Python
python 基于selectors库实现文件上传与下载
2020/12/31 Python
使用sublime text3搭建Python编辑环境的实现
2021/01/12 Python
基于Html5实现的react拖拽排序组件示例
2018/08/13 HTML / CSS
安全检查管理制度
2014/02/02 职场文书
六个一活动实施方案
2014/03/21 职场文书
工程承包协议书
2014/04/22 职场文书
个人租房协议书(范本)
2014/10/14 职场文书
2014离婚协议书范文(3篇)
2014/11/29 职场文书
医药公司采购员岗位职责
2015/04/03 职场文书
培根随笔读书笔记
2015/07/01 职场文书
2016教师六五普法学习心得体会
2016/01/21 职场文书
高三数学复习备考教学反思
2016/02/18 职场文书
NASA 机智号火星直升机拍到了毅力号设备碎片
2022/04/29 数码科技
CSS控制继承中的height能变为可继承吗
2022/06/10 HTML / CSS