美文网首页
JavaScript单线程事件循环及多线程实现

JavaScript单线程事件循环及多线程实现

作者: 环零弦 | 来源:发表于2017-12-28 15:28 被阅读0次

    一、JavaScript的单线程特点

    JavaScript最初设计是用来在浏览器中操作DOM的,所以JavaScript的单线程特点也是为了避免多线程时带来的同步问题。

    二、JavaScript单线程事件循环

    JavaScript单线程事件循环特点如下

    • JavaScript是单线程的,这个线程中拥有唯一的一个事件循环
    • JavaScript 代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。
    • 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个
    • 任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
      • 同步任务:指在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
      • 异步任务:指不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程某个异步任务可以执行了,该任务才会进入主线程执行。
    • 任务队列又分为macro-task(宏任务)与micro-task(微任务),它们被分别称为taskjobs
    • macro-task包括如下六个部分:
      • script(整体代码),
      • setTimeout,
      • setInterval,
      • setImmediate,
      • I/O,
      • UI rendering。
    • micro-task包括如下四个部分:
      • process.nextTick,
      • Promise,
      • Object.observe(已废弃),
      • MutationObserver(html5新特性)。
    • setTimeout/Promise等我们称之为任务源,而进入任务队列的是他们指定的具体执行任务
    • 来自不同任务源的任务会进入到不同的任务队列。其中setTimeoutsetInterval同源的
    • 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环
      • 之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。
    • 其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。

    三、例子

    setTimeout(function () {
      console.log('timeout1');
    })
    
    new Promise(function (resolve) {
      console.log('promise1');
      for (var i = 0; i < 1000; i++) {
        i == 99 && resolve();
      }
      console.log('promise2');
    }).then(function () {
      console.log('then1');
    })
    
    console.log('global1');
    

    上面代码的执行顺序是什么?

    1. 代码执行遇到setTimeout,setTimeout为一个宏任务源,那么他的作用就是将任务分发到它对应的队列中。
    2. script执行时遇到Promise实例。Promise构造函数中的第一个参数,是在new的时候执行,因此不会进入任何其他的队列,而是直接在当前任务直接执行了,而后续的.then则会被分发到micro-task的Promise队列中去。 因此输出:promise1、promise2。
    3. script任务继续往下执行,最后只有一句输出了globa1,然后,全局任务就执行完毕了。
    4. 第一个宏任务script执行完毕之后,就开始执行所有的可执行的微任务。这个时候,微任务中,只有Promise队列中的一个任务then1,因此直接执行就行了,执行结果输出then1。
    5. 当所有的micro-tast执行完毕之后,表示第一轮的循环就结束了。这个时候就得开始第二轮的循环。第二轮循环仍然从宏任务macro-task开始。这个时候,我们发现宏任务中,只有在setTimeout队列中还要一个timeout1的任务等待执行。因此就直接执行即可。
    所以输出是这样的:

    promise1
    promise2
    global1
    then1
    timeout1
    

    四、JavaScript多线程的实现

    test.html

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8" />
      <title>JavaScript Multiple Processes Test</title>
    </head>
    
    <body>
      <input type="text" name="haorooms" id="haorooms" value="" />
      <button id="start">start</button>
      <button id="stop">stop</button>
      <button id="ale">alert</button>
      <script type="text/javascript">
        var haorooms = document.getElementById("haorooms"),
            stop = document.getElementById("stop"),
            start = document.getElementById("start"),
            ale = document.getElementById("ale"),
            worker;
        stop.addEventListener("click", function () {
          //用于关闭worker线程
          worker ? worker.terminate() : {};
        });
        start.addEventListener("click", function () {
          //开起worker线程
          worker = new Worker("test.js");
          worker.onmessage = function () {
            haorooms.value = event.data;
          };
        });
        ale.addEventListener("click", function () {
          alert("i'm a alert worker test");
        });
      </script>
    </body>
    
    </html>
    

    test.js

    var i = 0;
    function mainFunc(){
        i++;
        //把i发送到浏览器的js引擎线程里
        postMessage(i);
    }
    var id = setInterval(mainFunc,1000);
    

    参考资料

    相关文章

      网友评论

          本文标题:JavaScript单线程事件循环及多线程实现

          本文链接:https://www.haomeiwen.com/subject/bhfvgxtx.html