Web Worker -- Thread.js 多线程计算任务
TIP
以下示例展示的是多线程请求图片资源,而后做图片相似度匹配的部分逻辑。 图片比较工具使用的是 pixelmatch.js
一、依赖安装与配置
shell
# 安装threads node 和 浏览器可用
npm install threads tiny-worker
# 安装webpack插件。默认的worker.js 只支持es5,无法使用import 和 require。
npm install -D threads-plugin
vue.config.js
js
// vue.config.js
// 引入
const ThreadsPlugin = require('threads-plugin')
// 配置plugins
configureWebpack: {
resolve: {
alias: {
"@": resolve('src')
}
},
plugins:[ new ThreadsPlugin()]
}
二、业务逻辑
如果计算量很大,我们不可能无限制的开线程,因此需要使用
队列
+线程池
的思想。由此,我们使用Thread.js。 https://threads.js.org/
main线程
js
// 在处理业务逻辑的页面加入如下代码
// mock data
const eventData = {
img1: "https://www.baidu.com/202103/1efbc777522e4279926b9acc02d4d1a7.jpg",
img2: "http://www.baidu.com/uca/UCA-023/600_c/f4f4f4.jpg",
listId: "1-PABSFWRWER0123",
threshold: 0.1,
}
// 【step1】
// 左侧图片下载,转为blob。此操作只需要在每次点击row时执行。
// 创建worker对象
const workerFun = await spawn(new Worker("@/worker/ThreadWorkerMulti"))
// 调用workerjs中暴露的getImageBlob function()
const blob = await workerFun.getImageBlob(eventData.img1)
// 完成后关闭worker线程
await Thread.terminate(workerFun);
// 【step2】
// 右侧图片相似度对比与计算
// mock data
const rightSearchList =[];
rightSearchList.push(eventData);
// 实际业务
const myTasks= []
// 声明线程池
const pool = Pool(() => spawn(new Worker("@/worker/ThreadWorkerMulti")),{ concurrency:4,name:"task-img-diff"})
for (let i = 0; i < rightSearchList.length; i++) {
// 模拟传参
const task = pool.queue(worker => worker.doImgDiff(blob,eventData.img2,eventData.listId,eventData.threshold))
.then()
// error 输出
.catch((error)=> {
console.log("error ==", error)
})
.finally()
myTasks.push(task);
}
// 返回结果result list
const result = await Promise.all(myTasks)
.finally(()=>{
// 关闭线程池
pool.completed();
pool.terminate();
})
/**
* 注意异常,undefined 的判断
* 返回值data
* [{
* listId: "1-PABSFWRWER0123",
* percent: 43.4
* }]
*/
console.log("pools result = ", result)
worker线程
js
// ThreadWorkerMulti.js
/* eslint-disable */
import {expose} from "threads"
import pixelmatch from 'pixelmatch'
/**
*
* @param tmpBlob 源图片blob
* @param img2Url 要比对的图片url地址
* @param listId 唯一标识id
* @param threshold 系数
*/
async function doImgDiff(tmpBlob, img2Url, listId, threshold) {
// 源图片,即左侧图片
// const tmpBlob = await getImageBlob(img1Url)
// 目标图片,即右侧分词查询list中的图片
const tmpBlob2 = await getImageBlob(img2Url)
const imageBitmap = await self.createImageBitmap(tmpBlob);
const imageBitmap2 = await self.createImageBitmap(tmpBlob2);
let canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
// willReadFrequently == > Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true warnings
let ctx = canvas.getContext('2d',{ willReadFrequently: true});
const diff = ctx.createImageData(imageBitmap.width, imageBitmap.height)
const imageData = getImageData(imageBitmap, ctx, canvas);
const imageData2 = getImageData(imageBitmap2, ctx, canvas);
const score = getImgScore(imageData, imageData2, diff, threshold, listId)
return score;
}
/**
* 获取图片blob二进制流
* @param url
* @returns {Promise<unknown>}
*/
function getImageBlob(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
url = "https://www.baidu.com/images?imageUrl=" + url
xhr.open("get", url, true);
xhr.responseType = "blob";
xhr.onload = function () {
if (this.status == 200) {
resolve(this.response);
} else {
reject(new Error('error-'));
}
};
xhr.send();
xhr.onerror = () => {
reject(new Error('error-'));
}
})
}
/**
* 计算图片相似度
* @param imageData1
* @param imageData2
* @param diff
* @returns {number|number}
*/
function getImgScore(imageData1, imageData2, diff, _threshold, _listId) {
const score = pixelmatch(imageData1.data, imageData2.data, diff.data, imageData1.width, imageData1.height, {threshold: _threshold})
const percent = ~~(score / (imageData1.width * imageData1.height) * 1000) / 10
const result = {
listId: _listId,
percent: percent
}
console.log("result ===", result)
return result;
}
function getImageData(imageBitmap, ctx, canvas) {
ctx.drawImage(imageBitmap, 0, 0, canvas.width, canvas.height)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
return imageData;
}
// 要导出的函数
const exposeFun={
doImgDiff, getImageBlob
}
expose(exposeFun)