webworker是啥
基于多线程
javascript采用的是单线程模型,所有的任务都在一个主线程中完成,一次只能执行一个任务,如果有多个任务需要被执行的话,那么后面的任务会依次排队等着
浏览器创建一个后台线程来执行JavaScript代码。底层实现是通过JS文件创建一个Worker对象,不会影响主线程性能。
- 需要线程通信进行数据传输。单向通信
- Worker 线程有独立的内存空间,Message Queue,Event Loop,Call Stack 等, Worker 线程和主线程内存独立,可以处理indexDB
- 处理大数据处理,例如图像处理、音频处理、视频编码、大文件传输等等。也可以用于实现实时通信、数据同步和数据可视化等等
- Worker线程一旦创建成功了,就会始终运行了,不会被主线程的运行打断,虽然这样有利于随时响应主线程的通信,但是这也造成了Worker比较耗费资源,在我们编码过程中,可以适当的使用,并且如果使用完成后,我们应该需要关闭
- 同源限制、文件限制,不能操作window\document,无法读取本地文件
数据传输问题
- Structured Clone: 序列化和反序列化,很多对象直接丢失数据
- Transfer Memory: 固定数据类型,移交使用权,移交之后,留下新对象
- Shared Array Buffer: 共享数据,需要加锁,少数浏览器支持
借鉴 博客
问题: canvas获取base64导致block
demo代码如下
var context = canvas.getContext("2d") image.onload = ()=> { canvas.width = image.width canvas.height = image.height this.image = image context.drawImage(image, 0, 0) setTimeout(() => { let time = Date.now() if (canvas.toBlob) { canvas.toBlob(function (blob) { var formData = new FormData() formData.append('file', blob, 'image.jpg') console.log("time1 = ", Date.now() - time) // 20ms
// const reader = new FileReader(); // reader.readAsDataURL(blob); // reader.onloadend = () => { // reader.result // console.log("time = ", Date.now() - time) // 20ms // } // time = Date.now() - time // console.log("time = ", time) // ... }, 'image/jpeg') } setTimeout(() => { time = Date.now() canvas.toDataURL('image/png') time = Date.now() - time console.log("toDataURL time = ", time) // 64 }, 3000);
}, 2000);
}
基本上 toBlob
的时间要比 toDataURL
要快 (image: 100 kB)
- 如果要上传图片文件,FormData 基本上只要 20ms左右 即可
- 如果要
toBlob
下转base64,则需要 20ms 左右, 产物 100 kB 左右 - 如果需要同步处理,
toDataURL
需要60ms左右, 产物 800 kB 左右 - 时间因图片大小有差异
加大图片大小 (image: 4 MB)
- 如果要上传图片文件,FormData 基本要 170ms左右 即可
- 如果要
toBlob
下转base64,则需要 170ms 左右, 产物 1M 左右 - 如果需要同步处理,
toDataURL
需要 600ms左右, 产物 png(8M) jpeg(1M) jpg(8M)左右
浏览器有明显卡顿,如何优化?
webworker介入
demo
let time = Date.now()
let imageBitMap = await createImageBitmap(this.image) // image 对象
time = Date.now() - time
console.log("time transform = ", time) // 100ms
let time = Date.now()
let imageBitMap = await createImageBitmap(canvas)
console.log("time transform = ", Date.now() - time) // 极低,10ms以内
利用数据通信,ImageBitMap 可以移交数据权限,并image、canvas在主线程不受影响,即createImageBitmap实现数据copy、transform
worker.js
return new Promise((res, rej)=>{
let imageBitMap = param.imageBitMap
let offscreenCanvas = new OffscreenCanvas(imageBitMap.width, imageBitMap.height)
let ctx = offscreenCanvas.getContext("2d");
ctx.drawImage(imageBitMap, 0, 0)
offscreenCanvas.convertToBlob({type: 'image/jpeg'}).then((blob) => {
const reader = new FileReader();
console.log("convertToBlob = ", blob)
reader.readAsDataURL(blob);
reader.onloadend = () => {
param.imageBase64 = reader.result
res(param);
}
})
})
- 最终获得base64,可以转成数据权限 ArrayBuffer, 再移交到主线程。
- 这地方transfer是移交对象组成的数组,移交之后准确的放置到对象的原相同位置,猜测是通过指针处理,又有沙箱处理,使用原地址不可访问;或者对原位置进行初始化原对象类型。
- 再一个是会检测引用计数,或者智能指针使用记录,在获取该对象的引用过程中,出现被其他对象占有并不可释放,将会报错。需要深拷贝
多个webworker的通信
每一个worker都是一个线程,属于渲染进程(一个标签)的线程,worker之间可以使用MessageChannel进行通信:
// index.html ====worker1.js // worker1.js self.onmessage = function(e) { const port = e.ports[0]; port.postMessage("this is from worker1") //woker1中通过port1发送给port2 }
====worker2.js
// worker2.js
self.onmessage = function(e) {
const port = e.ports[0];
port.onmessage = function(e) {
postMessage(e.data) //woker2中监听woker1中port1传递的数据,然后触发自身woker的监听函数
}
}
- port1、port2只支持移交使用权一次,如果二次移交,就会报错
- 管道通信,即先进先出消息缓冲区(内存),也叫信息消费
- 管道通信是单向的,MessageChannel是双向的
- onmessage默认start,addEventListener需要手动start
- onmessage只有一个,addEventListener可以多个
结合wasm处理
在worker里引入wasm,并初始化
- 使用import('path')遇到的问题
Failed to execute 'importScripts' on 'WorkerGlobalScope'
解决
A、大概率是设置了publicPath: './'
修改为'/',或者不设置,默认就是'/'
项目绕不开'./',这时候就要改为 require('path')
import最终在babel下会转为promise+require,常常作为异步加载来使用
通常不直接import { } from 'path',这个方式会直接解构wasm文件,并不会达到运行时加载wasm的效果,导致无法使用wasm
wasm例子: 伪代码
// wasm.js
xxxxxxx,xxx,ss;sss;ss;
// wasm生成,最后手动添加 export
export default Module;
// use.js
const cWasm = require('./wasm.js');
let cWasmModule = {};
function loadWasm() {
// no-async-promise-executor
return new Promise(async resolve=>{
cWasmModule = await cWasm;
cWasmModule.default.onRuntimeInitialized = () => {
resolve();
};
})
}
export function buildxxxx() {
return cWasmModule.default.buildFromC();
}
// worker.js import { buildxxxx, loadWasm } from './use.js'
loadWasm()
self.addEventListener(‘message’, async (e) => { // 接收到消息
let data = e.data || {}
// console.log('message worker = ', data)
let res = {}
let list =
res.imageBitMap = await workerBuild()
if(res.imageBitMap){
list = [res.imageBitMap]
}
self.postMessage(res, list); // 向主线程发送消息
});
async function workerBuild(data={}){
let res = buildxxxx()
let imageBitMap = await createImageBitmap(canvas)
return imageBitMap
}
这是一个从 https://juejin.cn/post/7368770335226019875 下的原始话题分离的讨论话题