0
点赞
收藏
分享

微信扫一扫

知道html5 Web Worker标准吗?能实现JavaScript的多线程?

js为什么是单线程?

主要是因为最开始javascript是单纯的服务于浏览器的一种脚步语言(那时候没有nodejs)。浏览器是为了渲染网页,通过dom与用户交互,如果一个线程需要给dom执行click事件,而另一个进程要删除这个dom,这2个动作可能同时进行,也可能先后进行(像java,c#等语言中会引入锁的概念,这样会变得异常复杂),那么就会造成很多不可预料的错误。

浏览器是多线程的

浏览器打开一个tab,就会单独开一个进程,这个进程包含多个线程,参考:JS运行机制
主要包含的线程有:

  1. GUI渲染线程
  1. JS引擎线程
  1. 事件触发线程
  1. 定时触发器线程
  1. 异步http请求线程

上面列出的线程之间,有一个重要的规则是:GUI渲染线程与JS引擎线程互斥,那么我们可以得出以下结论JS阻塞页面加载,那么在js运行的这段时间内,GUI的渲染会停止,这段时间内的界面交互,DOM的重绘与回流会停止,会被保存到待执行队列中,直到js线程空闲,才会执行这些队列。
我们用下面的一段代码和运行结果来说明这个机制:

<html>
<head>
    <style>
        .box {
            width: 200px;
            height: 200px;
            margin-top: 100px;
            background: #f09;
            animation: bounce 2s linear 0s infinite alternate;
            background-image: linear-gradient(45deg, #3023AE 0%, #f09 100%);
        }
        @keyframes bounce {
            0% {
                border-radius: 40% 60% 72% 28% / 70% 77% 23% 30%;
            }
            100% {
                border-radius: 75% 25% 24% 76% / 13% 15% 85% 87%;
            }
        }
    </style>
</head>
<body>
    <div class="box"></div>
</body>
<script>
    // 计算斐波那契数列,这个数列从第3项开始,每一项都等于前两项之和。
    function recurFib(n) {
        if (n < 2) {
            return n;
        } else {
            return recurFib(n - 1) + recurFib(n - 2)
        }
    }

    window.onload = function () {
        setTimeout(function () {
            console.time("运算耗时:")
            // 计算n为40的结果
            console.log('结果:', recurFib(40))
            console.timeEnd("运算耗时:")
        }, 2000)
        document.getElementsByClassName("box")[0].addEventListener('click', function () {
            console.log('click')
        })
    }
</script>
</html>

可以看到,一开始网页和动画正常运行,但是开始执行计算斐波那契数列后,动画就停止了,页面也停止响应鼠标的click事件了,直到recurFib(40)计算出结果后,动画才开始继续执行,而期间积攒的click事件也在一起被执行。这就解释了GUI渲染线程与JS引擎线程互斥。由于这个弊端HTML5提出Web Worker标准。

利用Web Worker开启一个子线程

Web Worker 有以下几个使用注意点。

以上规则引用阮一峰老师的: Web Worker 使用教程
创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)。
下面我们用worker的相关api来解决上面卡顿的问题。

<!--index.html主线程-->
<html>
<head>
    <style>
        .box {
            width: 200px;
            height: 200px;
            margin-top: 100px;
            background: #f09;
            animation: bounce 2s linear 0s infinite alternate;
            background-image: linear-gradient(45deg, #3023AE 0%, #f09 100%);
        }
        @keyframes bounce {
            0% {
                border-radius: 40% 60% 72% 28% / 70% 77% 23% 30%;
            }
            100% {
                border-radius: 75% 25% 24% 76% / 13% 15% 85% 87%;
            }
        }
    </style>
</head>
<body>
    <div class="box"></div>
</body>
<script>
    window.onload = function () {
        // 创建一个子线程worker实例
        var worker = new Worker('./test.js');
        setTimeout(function () {
            // 通信:向子线程发送消息
            worker.postMessage('start')
        }, 2000)
        worker.addEventListener('message', function(res) {
            //  通信:收到子线程消息
            console.log('result:',JSON.stringify(res.data));
            // 关闭worker线程
            worker.terminate();
        })
        document.getElementsByClassName("box")[0].addEventListener('click', function () {
            console.log('click')
        })
    }
</script>
</html>
// test.js子线程代码
// 通过监听message来接受主线程中的消息
addEventListener('message', function(res) {
    // 子线程向主线程中发生消息
    // 计算斐波那契数列,这个数列从第3项开始,每一项都等于前两项之和。
    if(res.data === 'start') {
        // 开始运算
        console.log('收到主线程消息,开始运算')
        function recurFib(n) {
            if(n < 2){
                // 主动关闭子线程
                // this.close()
                return n ;
            }else {
                return recurFib(n-1)+recurFib(n-2)
            }
        }
        console.time("运算时间:")
        // 计算n为40的结果
        var count = recurFib(40)
        console.timeEnd("运算时间:")
        // 向主线程发送消息
        console.log('运算完毕,发送消息给主线程!')
        this.postMessage(count);
    }
})

运行结果:


可以看到整个运行过程动画没有卡顿,也能响应click事件,所以在我们遇到大型计算的时候,请单独开启一个worker子线程来解决js线程阻塞GUI线程的问题。上文中只涉及到一部分worker API。关于worker更详细更具体的用法可以参见: Web Worker 使用教程

兼容性


可以看到除了Opera Mini浏览器,连IE都能使用了,所以兼容性问题不大。

总结

  1. 由于javaScript的最初设计特点,采用了单线程的运行机制。
  2. 浏览器是多个线程相互协作来工作的,但是GUI渲染线程与JS引擎线程互斥
  3. js线程在运行时,会锁死GUI渲染线程,为了利用多核CPU的计算能力,HTML5提出Web Worker标准。
  4. Web Worker的使用有一些限制,比如说:同源限制,DOM限制,文件限制等,但能解决在js需要大量计算工作时,页面卡顿的问题。
  5. Web Worker实际上是js线程的一个子线程,理论上js还是单线程的。
举报

相关推荐

0 条评论