浅谈 web worker(多线程)

1,041 阅读4分钟

一、背景

众所周知,js是单线程的,有时候在处理数据量较大的业务时,会造成线程阻塞,就会影响用户使用体验,出现卡顿等情况。针对这个缺点,H5推出了web worker,使JS支持多线程。

二、优点

web worker 可以创建子线程,子线程会在一个全新的环境中执行,你可以在执行子线程的同时,去做任何事情,即不会影响到主线程的正常执行,也不会影响页面的性能与用户体验。

三、应用场景

  • 大图片canvas转base64的时候
  • 对大量数据进行加密时
  • 读取解析一个较大的json文件
  • 在做一些插件时,比如代码的拼写检查,代码高亮,实时文本等
  • 处理音视频数据
  • 处理一些较大的数组集合
  • ...

四、使用

1. 子线程

(1)先创建一个子线程文件:child.js

(2)再监听message事件,以获取主线程传过来的数据

(3)使用postMessage方法回传数据给主线程,以达到通知的目的

// 接收主线程信息
// 这里的this指向子线程本身
this.addEventListener('message', (e) => {
    console.log('主线程传过来的数据:', e.data)
    postMessage('子线程收到数据了')
})

2. 主线程

(1)先做一个兼容性判断,毕竟有些浏览器不支持web worker

(2)创建子线程实例

(3)向子线程发送数据

(4)监听子线程回传的信息

// 兼容性判断
if (!Worker) throw new Error('当前浏览器不支持多线程')

// 创建子线程,参数为子线程脚本文件地址
const childThread = new Worker('child.js')

// 向子线程发送数据
childThread.postMessage('你好子线程')

// 监听子线程信息
childThread.addEventListener('message', (e) => {
    console.log('子线程回传的数据: ', e.data)
})

3. 演示结果

image.png

可以看到,输出结果分别来自子线程和主线程

4. 关闭线程

当线程结束时,我们需要手动进行关闭,否则会一直运行在后台

主线程中关闭子线程:子线程实例名称.terminate()

子线程内部关闭自己:this.close()

5. 监听错误处理

当子线程发生错误时,主线程可以通过监听error事件来处理:

子线程实例名称.addEventListener('error', (e) => {...})

6. 子线程中引入文件

有时,我们需要在子线程中引入其他脚本文件:

importScripts('script1.js', 'script2.js')

7. 多线程嵌套

子线程中可以再次通过new Worder('xxx')去实例化一个属于自己的子线程,实现线程嵌套,以处理复杂业务。

五、小试牛刀

我们用for循环来简单模拟处理大量数据的场景,来看看不使用worker与使用worker的情况有何不同

1. 不使用worker的情况

console.log('执行开始')
console.time("执行用时")
let arr = []
for (let i = 0; i < 100000000; i++) {
    arr.push(i)
}
console.timeEnd("执行用时")
console.log('该我执行了')

运行结果如下:

image.png

我们可以看到,这段for循环执行了800多毫秒,这在计算机看来,运行时间是非常久的,并且要等for循环执行完,才能继续执行后续的代码,如果数据量再大一点,那么就必然会造成线程阻塞,甚至导致浏览器崩溃。

2. 使用worker的情况

子线程代码:

// 接收主线程信息
this.addEventListener('message', (e) => {
    console.log('主线程传过来的数据:', e.data)
    let arr = []
    for (let i = 0; i < e.data; i++) {
        arr.push(i)
    }
    postMessage(arr)
})

主线程代码

console.log('执行开始')
console.time("执行用时")

// 兼容性判断
if (!Worker) throw new Error('当前浏览器不支持多线程')

// 创建子线程
const childThread = new Worker('child.js')

// 向子线程发送数据
childThread.postMessage(10000000)

// 监听子线程信息
childThread.addEventListener('message', (e) => {
    console.log('子线程回传的数据: ', e.data)
})

console.timeEnd("执行用时")
console.log('该我执行了');

运行结果如下:

image.png

我们可以看到,执行到 “该我执行了” 这段代码时,中间仅花了0.08毫秒左右,也就是说,这中间并不需要去等待worker的执行,等子线程执行完任务后,就会回传给主线程,主线程并不用担心子线程需要执行多久,结果是否失败,都不会影响到主线程的正常执行,这也就印证了web worker多线程的强大之处。

六、注意事项

  • 主线程和子线程之间传递的消息是通过复制的方式,而不是共享,也就是说如果传递参数是一个对象,那么程序会使用JSON方式来编码/解码对象
  • 主线程和子线程是不同上下文,不同作用域,不同环境,子线程不能操作主线程的dom和方法,比如:window、document、alert等对象

七、总结

以上就是关于web worker的使用与应用场景的简单模拟,以后在遇到类似业务场景时,我们就可以使用web worker去解决。希望本文能够对大家有所帮助,谢谢!

结合本文,出一道相信大家都听到过的面试题——如果后端一次性给你返回十万条数据,你会怎么处理?