美文网首页程序员前端那些事儿
javascript的单线程事件循环及多线程介绍

javascript的单线程事件循环及多线程介绍

作者: 茹茹茹茹茹茹欧尼_ | 来源:发表于2017-07-12 21:31 被阅读0次

    前言


    其实我前面文章对于改变js的执行顺序及多线程都有相关介绍!例如,我们可以用[setTimeout(fn,0)]改变代码执行循序,文章最后也提及了Event Loop(事件循环)。同时,js可以模拟实现多线程。这篇文章,我详细介绍一下js单线程事件循环,及多线程的实现。

    JavaScript单线程原因


    JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。这和JavaScript的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。例如:假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

    什么是js多线程


    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

    js单线程事件循环


    针对事件循环机制,有如下总结:

    一、javascript是单线程的,这个线程中拥有唯一的一个事件循环。

    二、JavaScript 代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(taskqueue)来搞定另外一些代码的执行。
    enter.png
    三、一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。

    四、任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务

    (asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务(见上图);异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。


    五、任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

    六、macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

    七、micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

    八、setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。

    九、来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

    十、事件循环的顺序,决定了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');
    

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

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

    js多线程的实现

    我们先看一个例子:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title></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 = new Worker("js/haorooms_test.js");
                worker.onmessage = function(){
                    haorooms.value = event.data;
                };
                stop.addEventListener("click",function(){
                    //用于关闭worker线程
                    worker.terminate();
                });
                start.addEventListener("click",function(){
                    //开起worker线程
                    worker = new Worker("js/haorooms_test.js");
                });
                ale.addEventListener("click",function(){
                    alert("i'm a alert worker test");
                });
            </script>
        </body>
    </html>
    

    下面是text.js里的代码,也就是存在于worker线程里的代码。

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

    虽然alert的弹出框会阻塞js引擎线程,但是并不影响worker线程的运行,所以,在我们点击确定后,只是在js引擎线程上更新了新的内容,而数值是一直在跑动的,这就说明worker线程和原本的js线程互不影响。

    这就是简单的js多线程的实现。

    相关文章

      网友评论

        本文标题:javascript的单线程事件循环及多线程介绍

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