在之前写阅读器的时候有用到多线程:案例——《享阅·阅读器》解析。但是阅读器里我只开启了一个线程,所以并不能说真的使用了多线程。最近,在一个已经放弃的应用(能力所限,写不出来😂)里才尝试使用了多个线程。
基本的文件结构和阅读器里的差不多。首先还是安装 worker-loader 并配置。(框架是 vue)
module.exports = {
chainWebpack: config => {
config.module.rule('js').exclude.add(/\.worker\.js$/)
config.module
.rule('web-worker')
.test(/\.worker\.js$/)
.use('worker-loader')
.loader('worker-loader')
.end()
config.output.globalObject('this')
}
}
然后,utils 文件夹下面建立三个文件:utils.worker.js、promiseWorker.js、noDomUtils.js。
utils.worker.js 就是 worker 入口文件,会引入 worker 中需要用到的函数。
// utils.worker.js
import * as utils from '@/utils/noDomUtils'
// worker 收到信息并执行相关操作
onmessage = (e) => {
const { action, param = [], timestamp, _sign } = e.data
if (typeof utils[action] === 'function') {
const res = {
action,
result: utils[action](...param),
timestamp,
_sign
}
postMessage(res)
} else {
console.log(`指定操作${action}不存在`)
}
}
promiseWorker.js 是对 worker 通信的封装。相比于回调,promise 会更方便。多线程管理也在这里。
import Worker from '@/utils/utils.worker'
import { config } from '@/utils/setting'
const workerNum = config.threads // 线程数量
const quene = new Map() // 计算队列
const waiting = [] // 等待队列
const workers = new Array(workerNum).fill(null).map((_, index) => {
return (
{
index,
worker: new Worker(),
idle: true, // 是否空闲
}
)
})
workers.map(item => {
item.worker.addEventListener('message', e => {
if (!e.data || !e.data._sign) {
console.error('worker 返回数据错误')
// quene.get(e.data._sign).reject('worker 返回数据错误')
} else {
quene.get(e.data._sign).resolve({
result: e.data.result,
e
})
}
quene.delete(e.data._sign)
item.idle = true
// 尝试接受新任务
assignJob()
})
})
/**
* 将等待队列中的任务加入空闲线程
*/
function assignJob() {
let idleWorker = null
let waitingJob = null
if (waiting.length) {
idleWorker = workers.find(item => item.idle)
if (idleWorker) {
idleWorker.idle = false
waitingJob = waiting.shift()
quene.set(waitingJob._sign, waitingJob.p)
console.log(idleWorker.index)
idleWorker.worker.postMessage({
...waitingJob.job,
_sign: waitingJob._sign
})
}
}
}
/**
* @param job {
* action: '',
* param: {},
* timestamp // 可选
* }
*/
export default (job) => {
job.timestamp = job.timestamp || Date.now()
return new Promise((resolve, reject) => {
const _sign = Date.now() * Math.random()
waiting.push({
_sign,
job,
p: { resolve, reject }
})
// 分配线程
assignJob()
})
}
noDomUtils.js 里面是可以在 worker 里运行的函数。这里不贴了。
为了方便,我将 worker 挂在了 vue 原型上。类似代码如下:
// main.js import Vue from 'vue' import promiseWorker from '@/utils/promiseWorker' Vue.prototype.worker = promiseWorker
因为 promiseWorker.js 无法得知哪些任务可以分割以及如何分割,所以业务代码自行分割任务。比如:
const res = await Promise.all(jobs.map(job => {
return this.worker({
action: 'xxx',
param: [param1, param2]
})
}))
web worker 赋予了 JavaScript 密集计算的能力。但是这个功能还是比较鸡肋。一般情况下,web 的瓶颈在网络,不在密集计算。并且,postMessage 传输时间可能会超过计算时间,特别是数据层级较多的时候。虽然可以使用可传输对象,但可传输对象类型有限制。除了图像、音频、视频处理和游戏等领域,其他场景很少会用到 web worker。