在之前写阅读器的时候有用到多线程:案例——《享阅·阅读器》解析。但是阅读器里我只开启了一个线程,所以并不能说真的使用了多线程。最近,在一个已经放弃的应用(能力所限,写不出来😂)里才尝试使用了多个线程。
基本的文件结构和阅读器里的差不多。首先还是安装 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。