zoukankan      html  css  js  c++  java
  • 单线程&浏览器多线程

    知乎答案: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('calclation done')放入事件队列。根据先进先出,
      //$('#status').text()先变成了 calculating,然后迅速变成calcation done。所以看不到calculating。
      // with set timeout, works as expected
      setTimeout(long,50);//用定时器,大约50ms以后执行长时间任务,放入执行队列,但在redraw之后了,根据先进先出原则
      //这种情况是:$('#status').text('calculating....')首先放入事件队列,setTimeout(),50ms后,long放入事件队列.根据先进先出,执行$('#status').text('calculating....')。
      //执行long,循环完毕,$('#status').text('calclation done')放入事件队列,然后执行。setTimeout的作用就是使$('#status').text('calculating....')先进入队列被执行,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文件并发下载没有多大意义。

    2014-02-17_16h16_34

    要实现非阻塞js(non-blocking javascript)有两个方法:1. html5 2. 动态加载js

    首先一种办法是HTML5的deferasync关键字:

    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);}
     
     
     
     
     
     
     
  • 相关阅读:
    P1144 最短路计数 题解 最短路应用题
    C++高精度加减乘除模板
    HDU3746 Teacher YYF 题解 KMP算法
    POJ3080 Blue Jeans 题解 KMP算法
    POJ2185 Milking Grid 题解 KMP算法
    POJ2752 Seek the Name, Seek the Fame 题解 KMP算法
    POJ2406 Power Strings 题解 KMP算法
    HDU2087 剪花布条 题解 KMP算法
    eclipse创建maven项目(详细)
    maven的作用及优势
  • 原文地址:https://www.cnblogs.com/darr/p/4468294.html
Copyright © 2011-2022 走看看