单线程&浏览器多线程
知乎答案:http://www.zhihu.com/question/31982417/answer/54136684
copy大牛的好文:from http://www.cnblogs.com/Mainz/p/3552717.html
http://coolshell.cn/articles/9749.html
只是做个记录,方便以后查看。
面试的时候发现99%的童鞋不理解为什么JavaScript是单线程的却能让AJAX异步发送和回调请求,还有setTimeout也看起来像是多线程的?还有non-blocking IO, event loop等概念很不清楚。来深入分析一下:
首先看下面的代码:
1
2
3
4
5
6
7
8
9
|
function foo() { console.log( 'first' ); setTimeout( ( function (){ console.log( 'second' ); } ), 5); } for ( var i = 0; i < 1000000; i++) { foo(); } |
执行结果会首先全部输出first,然后全部输出second;尽管中间的执行会超过5ms。为什么?
Javascript是单线程的
因为JS运行在浏览器中,是单线程的,每个window一个JS线程,既然是单线程的,在某个特定的时刻只有特定的代码能够被执行,并阻塞其它的代码。而浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,会创建事件并放入执行队列中。javascript引擎是单线程处理它的任务队列,你可以理解成就是普通函数和回调函数构成的队列。当异步事件发生时,如mouse click, a timer firing, or an XMLHttpRequest completing(鼠标点击事件发生、定时器触发事件发生、XMLHttpRequest完成回调触发等),将他们放入执行队列,等待当前代码执行完成。
异步事件驱动
前面已经提到浏览器是事件驱动的(Event driven),浏览器中很多行为是异步(Asynchronized)的,例如:鼠标点击事件、窗口大小拖拉事件、定时器触发事件、XMLHttpRequest完成回调等。当一个异步事件发生的时候,它就进入事件队列。浏览器有一个内部大消息循环,Event Loop(事件循环),会轮询大的事件队列并处理事件。例如,浏览器当前正在忙于处理onclick事件,这时另外一个事件发生了(如:window onSize),这个异步事件就被放入事件队列等待处理,只有前面的处理完毕了,空闲了才会执行这个事件。setTimeout也是一样,当调用的时候,js引擎会启动定时器timer,大约xxms以后执行xxx,当定时器时间到,就把该事件放到主事件队列等待处理(浏览器不忙的时候才会真正执行)。
每个浏览器具体实现主事件队列不尽相同,这不谈了。
浏览器不是单线程的
虽然JS运行在浏览器中,是单线程的,每个window一个JS线程,但浏览器不是单线程的,例如Webkit或是Gecko引擎,都可能有如下线程:
- javascript引擎线程
- 界面渲染线程
- 浏览器事件触发线程
- Http请求线程
很多童鞋搞不清,如果js是单线程的,那么谁去轮询大的Event loop事件队列?答案是浏览器会有单独的线程去处理这个队列。
Ajax异步请求是否真的异步?
很多童鞋搞不清楚,既然说JavaScript是单线程运行的,那么XMLHttpRequest在连接后是否真的异步?
其实请求确实是异步的,这请求是由浏览器新开一个线程请求(见前面的浏览器多线程)。当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到 JavaScript引擎的事件处理队列中等待处理。当浏览器空闲的时候出队列任务被处理,JavaScript引擎始终是单线程运行回调函数。javascript引擎确实是单线程处理它的任务队列,能理解成就是普通函数和回调函数构成的队列。
总结一下,Ajax请求确实是异步的,这请求是由浏览器新开一个线程请求,事件回调的时候是放入Event loop单线程事件队列等候处理。
setTimeout(func, 0)为什么有时候有用?
写js多的童鞋可能发现,有时候加一个setTimeout(func, 0)非常有用,为什么?难道是模拟多线程吗?错!前面已经说过了,javascript是JS运行在浏览器中,是单线程的,每个window一个JS线程,既然是单线程的,setTimeout(func, 0)神奇在哪儿?那就是告诉js引擎,在0ms以后把func放到主事件队列中,等待当前的代码执行完毕再执行,注意:重点是改变了代码流程,把func的执行放到了等待当前的代码执行完毕再执行。这就是它的神奇之处了。它的用处有三个:
- 让浏览器渲染当前的变化(很多浏览器UI render和js执行是放在一个线程中,线程阻塞会导致界面无法更新渲染)(这个阻塞和下面说的阻塞不是一回事。这个阻塞可以用setTimeout解决。下面的阻塞,用异步加载JS解决)
- 重新评估”script is running too long”警告
- 改变执行顺序
例如:下面的例子,点击按钮就会显示"calculating....",如果删除setTimeout就不会。因为reDraw事件被进入事件队列到长时间操作的最后才能被执行,所以无法刷新。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<button id= 'do' > Do long calc!</button> <div id= 'status' ></div> <div id= 'result' ></div> $( '#do' ).on( 'click' , function (){ $( '#status' ).text( 'calculating....' ); //此处会触发redraw事件的fired,但会放到队列里执行,直到long()执行完。 // without set timeout, user will never see "calculating...." //long();//执行长时间任务,阻塞 //这种情况是:$( '#status' ).text( 'calculating....' )首先放入事件队列,然后执行long,循环完毕, //
$( '#status' ).text()先变成了 calculating,然后迅速变成calcation done。所以看不到calculating。 // with set timeout, works as expected setTimeout(long,50); //用定时器,大约50ms以后执行长时间任务,放入执行队列,但在redraw之后了,根据先进先出原则 //这种情况是:$( '#status' ).text( 'calculating....' )首先放入事件队列,setTimeout(),50ms后,long放入事件队列.根据先进先出,执行 //执行long,循环完毕,
$( '#status' ).text( 'calclation done' )放入事件队列,然后执行。setTimeout的作用就是使 }) function long(){ var result = 0 for ( var i = 0; i<1000; i++){ for ( var j = 0; j<1000; j++){ for ( var k = 0; k<1000; k++){ result = result + i+j+k } } } $( '#status' ).text( 'calclation done' ) // has to be in here for this example. or else it will ALWAYS run instantly. This is the same as passing it a callback } |
非阻塞js的实现(non-blocking javascript)
js在浏览器中需要被下载、解释并执行这三步。在html body标签中的script都是阻塞的。也就是说,顺序下载、解释、执行。
尽管Chrome可以实现多线程并行下载外部资源,例如:script file、image、frame等(css比较复杂,在IE中不阻塞下载,但Firefox阻塞下载)。但是,由于js是单线程的,所以尽管浏览器可以并发加快js的下载,但必须依次执行。
每个javascript执行的时候还是同步的,就是先出现的script标签一定是先执行,即使是并行下载它最后一个下载完成。除非标有defer的script标签
所以chrome中image图片资源是可以并发下载的,但外部js文件并发下载没有多大意义。
要实现非阻塞js(non-blocking javascript)有两个方法:1. html5 2. 动态加载js
http://ued.ctrip.com/blog/script-defer-and-async.html
Defer对于defer,我们可以先思考一个情况。一个页面如果有N个外链的脚本,放在head中,那么,当加载(应该是执行吧)脚本时会阻塞页面的渲染,也就是常说的空白。在简单的开发环境中,我们可能只要将源代码中的外链脚本位置换一下就ok了。可是面对越来越复杂的开发环境,前端同事如果要后台开发同事调整一下脚本的位置,可能会花费大量的沟通成本和开发成本。我在去年的一个项目中就遇到过此类情况,当然也很感谢当时的后台开发同事的配合,他们都辛辛苦苦的调整了脚本的位置,解决了空白的问题。 那么可以让这个成本降到最低吗?那么我们可以使用defer这个属性。 如果一个script加了defer属性,即使放在head里面,它也会在html页面解析完毕之后再去执行,也就是类似于把这个script放在了页面底部。 |
Async对于async,这个是html5中新增的属性,它的作用是能够异步的加载和执行脚本,不因为加载脚本而阻塞页面的加载。一旦加载到就会立刻执行。那async和defer有什么不同之处呢?我们还是先看async的两个demo. 和defer一样,会等待的资源不会阻塞其余资源的加载,也不会影响页面的加载。但是有一点需要注意下,在有async的情况下,js一旦下载好了就会执行,所以很有可能不是按照原本的顺序来执行的。如果js前后有依赖性,用async,就很有可能出错。 |
defer
1
|
<script type= "text/javascript" defer src= "foo.js" ></script> |
async
1
|
<script type= "text/javascript" async src= "foo.js" ></script> |
然后第二种方法是动态加载js:
这样就不阻塞了么?我觉得这样也是阻塞的。那动态加载js的意义是什么?
图片及js的预加载
一个window一个js线程,当setTimeout延时很小的情况下,无论setTimeout()写在哪个js的什么位置,它里面的函数 执行的时间都是所有js执行完,事件队列中轮到它的时候,它才执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
setTimeout( function (){ var script = document.createElement( "script" ); script.type = "text/javascript" ; script.src = "foo.js" ; var head = true ; //加在头还是尾 if (head) document.getElementsByTagName( "head" )[0].appendChild(script); else document.body.appendChild(script); }, 0); //另外一个独立的动态加载js的函数 //另外一个独立的动态加载js的函数 function loadJs(jsurl, head, callback){ var script=document.createElement( 'script' ); script.setAttribute( "type" , "text/javascript" ); if (callback){ if (script.readyState){ //IE script.onreadystatechange = function (){ if (script.readyState == "loaded" || script.readyState == "complete" ){ script.onreadystatechange = null ; callback(); } }; } else { //Others script.onload = function (){ callback(); }; } } script.setAttribute( "src" , jsurl); if (head) document.getElementsByTagName( 'head' )[0].appendChild(script); else document.body.appendChild(script);} |
单线程&浏览器多线程的更多相关文章
- script 标签的defer,async的作用,及拓展浏览器多线程,DOMContentLoaded
前端优化有一点就是优化js的执行时机,一般做法是将script放置于body的结束标签,以避免加载执行js 文件导致页面渲染阻塞的问题这种做法确实能防止页面阻塞,但是在页面渲染完成之后才去加载js文件 ...
- JS异步解决方案之概念理解-----------阻塞和非阻塞,同步和异步,并发和并行,单线程和多线程
首先记住一句话,JS是单线程的. 单线程意味着什么?单线程意味着 它不能依靠自己实现异步. JS实现的异步,往往都是靠 浏览器.Node 的机制(事件驱动.回调)实现的. 下面让我这个单身狗 以谈恋爱 ...
- JavaScript是单线程还是多线程(转)
多线程要考虑线程之间的资源抢占,死锁,冲突之类一系列问题.JavaScript作为一门客户端脚本,貌似没有多线程的一些列问题.那么JavaScript是单线程还是多线程?通过查资料总结了JavaScr ...
- java归并排序,单线程vs多线程
一.什么是归并排序 归并排序又称合并排序,它是成功应用分治技术的一个完美例子.对于一个需要排序的数组A[0..n-1],归并排序把它一分为二:A[0..n/2-1]和A[n/2..n-1],并对每个子 ...
- python单线程,多线程和协程速度对比
在某些应用场景下,想要提高python的并发能力,可以使用多线程,或者协程.比如网络爬虫,数据库操作等一些IO密集型的操作.下面对比python单线程,多线程和协程在网络爬虫场景下的速度. 一,单线程 ...
- Python-爬取校花网视频(单线程和多线程版本)
一.参考文章 python爬虫爬取校花网视频,单线程爬取 爬虫----爬取校花网视频,包含多线程版本 上述两篇文章都是对校花网视频的爬取,由于时间相隔很久了,校花网上的一些视频已经不存在了,因此上述文 ...
- Python-爬取妹子图(单线程和多线程版本)
一.参考文章 Python爬虫之——爬取妹子图片 上述文章中的代码讲述的非常清楚,我的基本能思路也是这样,本篇文章中的代码仅仅做了一些异常处理和一些日志显示优化工作,写此文章主要是当做笔记,方便以后查 ...
- 面试之二:Redis是单线程还是多线程?以及处理模型。
Redis是单线程还是多线程?以及处理模型. 线程:单线程 处理模型:参考书<Redis 设计与实现>P151-152  将一个类的接口转换成客户希望的另外一个接口. Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 适配器 ...
- poj 2987 最大权闭合图
Language: Default Firing Time Limit: 5000MS Memory Limit: 131072K Total Submissions: 8744 Accept ...
- 【DIOCP知识库】连接上下文TIocpClientContext
来自弦子介绍 [概述] 该类管理远程连接,每一个远程连接会对应一个该类的实例,开发时可以通过继承该类,扩展属性,可以存储更多的连接信息,可以重写OnRecvBuffer方法进行数据逻辑的处理 [字段/ ...
- Spring RMI (Spring 远程方法调用)
所需jar包...? 不纠结,一股脑儿全导! 源码地址:http://pan.baidu.com/s/1jG8eOmy 先放结构图如下,客户端和服务端都在一个项目中.也可以把服务端的xxx导成j ...
- 手把手教你用axis1.4搭建webservice(转)
1.先下载axis jar包:axis-bin-1_4.zip.下载地址: http://ws.Apache.org/axis/. 当然这个包其实是不全面的,像activation.jar之类的,完全 ...
- Selenium2+python自动化31-生成测试报告
前言 最近小伙伴们总有一些测试报告的问题,网上的一些资料生成报告的方法,我试了都不行,完全生成不了,不知道他们是怎么生成的,同样的代码,有待研究. 今天小编写一下可以生成测试报告的方法.个人觉得也是最 ...
- IE、FF、Safari、OP不同浏览器兼容报告
IE.FF.Safari.OP不同浏览器兼容报告 1 浏览器内核简介 Trident IE浏览器(GreenBrowser绿色浏览器, 遨游浏览器....都是IE) Geckos Fi ...
- stream_copy_to_stream的使用
stream_copy_to_stream - 在数据流之间进行复制操作 例子: <?php //读写方式 $stream = fopen('php://temp', 'w+'); //如果成功 ...
- iOS 16进制字符串转换成int十进制
NSRange rangeErr; rangeErr.location = 6; rangeErr.length = 2; NSString *strings = [value substringWi ...
- linux安装,window上安装centos虚拟机
使用的软件如下 VMware_workstation_full_12.5.2.exeCentOS-6.5-x86_64-minimal.iso 安装要点: 1 先安装VMware2 建立一个虚拟机,注 ...