性能优化:H5的线程池先有问题再有答案 js不是单线程嘛 为什么又说多线程? 如何理解H5的多线程? 如何理解h5的wo
先有问题再有答案
js不是单线程嘛 为什么又说多线程?
如何理解H5的多线程?
如何理解h5的worker?
web worker 有什么特点?
有什么使用场景?
有什么限制?
多线程间是如何通讯?
背景
JavaScript 是单线程运行的,这意味着同一个时间只能做一件事情。由于用户交互、网络请求、IO操作等可能会产生阻塞,所以 JavaScript 引入了异步机制和事件循环的概念。
单线程&异步
异步可以看做是单线程面对需要大量耗时操作时的一种解决方案,它将这些耗时操作异步处理,也就是挂起这些事件,进行下一步的操作。当异步事件处理完成后,再通过回调函数的方式进行处理。所以,异步并不是在单线程之外开辟了新的线程,而是通过事件挂起和回调函数的方式使得 JavaScript 引擎不需要阻塞等待,可以先处理其他任务。
单线程&事件循环
事件循环是 JavaScript 实现异步的一种方法,它可以解决 JavaScript 单线程同步执行带来的阻塞问题。事件循环的机制是,将所有的任务分为宏任务和微任务,JavaScript 引擎先执行一个宏任务,紧接着执行所有的微任务,然后再执行下一个宏任务。这种模式不断循环,称为事件循环
单线程&多线程
JavaScript是单线程的,这是因为JavaScript设计初衷是处理简单的DOM操作,复杂和耗时的操作可能导致页面阻塞,影响用户体验。
然而,随着Web应用变得越来越复杂,JavaScript也需要处理更多的复杂且计算密集型任务
。为了解决这个问题,HTML5提出了Web Workers标准,允许JavaScript创建多个工作线程,这些工作线程在后台执行任务,不会影响主线程和用户界面的相应。
我们可以认为,JavaScript的主线程仍然是单线程的,但是通过Web Workers,JavaScript能够创建生成真正的操作系统级别的工作线程,实现多线程编程
。
web worker兼容性
线上完全可用 无兼容性问题。
使用场景
cpu密集型的任务
如果有耗时任务 在主线程中长时间占用cpu,这样的计算可能会阻塞用户界面卡顿,导致无法响应用户的操作。
测试代码如下:
const test = () => {
let count = 0;
for (let i = 0; i < 1000000000; i++) {
count = count + i;
}
};
test();
运行在主线程的任务性能分析如图:
主线程耗时12s左右 这期间左侧列表是卡死 不能响应用户滚动的
运行在worker线程
性能分析如图-1
可以看到当选中主线程时 js执行仅3毫秒
性能分析如图-2
当选中worker线程时 js执行耗时13s
运行在worker中的整个过程 左侧列表是可以响应用户滚动的, 这就是多线程的魅力 开启了一个工作线程 分担了主线程的耗时任务 有效的避免了卡顿。
周期性后台任务
使用限制
同源限制:
Web Workers 只能加载来自同源的脚本。也就是说,你不能在你的网站上使用一个来自其他域的脚本,这是出于安全原因。
不能访问 DOM
Web Workers 不能访问页面的 DOM。这是因为 DOM 操作不能用于多线程环境。目的是防止出现两个线程对 DOM 进行修改而造成的数据不同步。
不能访问某些对象:
Web Workers 不能访问window对象、document对象、parent对象等, 即使Web Workers不能访问这些对象,它们仍然可以发起AJAX请求或使用 WebSockets 这类网络API。 查看worker可以使用的api
使用方式
创建worker
const worker = new Worker('worker.js', {
type: 'module',
credentials: 'same-origin'
});
Worker构造函数接收两个参数:
-
url: 这是一个字符串值,表示一个指向 JavaScript 文件的 URL。这个参数是必须的,它的值应该是一个相对于当前 HTML 文档的相对路径,或者是一个完整的 URL。
-
options: 这是一个对象,用于配置新创建的 Worker 实例。这个参数是可选的,可以包含以下属性:
- type: 用于指定要创建的 Worker 的类型。支持的值有 "classic" 和 "module"。"classic" 表示 Worker 脚本是一个普通 JavaScript 文件,这是默认类型。"module" 表示 Worker 脚本是一个 JavaScript 模块。
- credentials: 用于指定在加载 Worker 脚本时是否需要证书(cookies)。支持的值有 "omit"、"same-origin" 和 "include"。
Blob对象创建worker
// 创建一个 Blob 对象
const blob = new Blob([
"onmessage = function(e) { postMessage('Worker: ' + e.data); }"
]);
// 创建一个 Blob URL
const blobURL = window.URL.createObjectURL(blob);
// 使用 Blob URL 创建一个 Worker
const worker = new Worker(blobURL);
worker.onmessage = function(e) {
console.log(e.data); // 输出 "Worker: Hello"
}
worker.postMessage('Hello'); // 向 worker 发送消息
多线程间通讯
Worker 线程和主线程不能直接共享内存或变量,它们之间的通信必须通过消息传递来完成。可以使用 postMessage
方法来发送消息,并在另一边使用 onmessage
事件监听器来接收消息。
worker.onmessage = function(e) {
console.log(e.data); // 输出 "Worker: Hello"
}
worker.postMessage('Hello'); // 向 worker 发送消息
实现一个线程池
本文提供一个通过web worker实现线程池的能力。
demo
export const workerStr = `
this.store = {};
this.addEventListener(
"message",
function (e) {
var key = e.data.key;
if(e.data.type === 'remove-item' && this.store[key]){
this.store[key] = null;
return;
}
if(e.data.type === 'remove'){
this.store = {};
return;
}
if (!e.data.task) {
return;
}
var task = new Function('return (' + e.data.task + ')(this.store)');
Promise.resolve(task())
.then((res) => {
this.store[key] = {
status: "succ",
data: res,
};
})
.catch((error) => {
this.store[key] = {
status: "fail",
data: error,
};
})
.finally(() => {
console.log('test worker res', {
key,
...this.store[key],
})
this.postMessage({
key,
...this.store[key],
});
});
},
false
);
`;
export default workerStr;
import { PoolList, Resolve, TaskRes } from '../type/task';
import workerStr from './worker'
let blob: Blob;
let url: string;
let threadPool: Worker[] = [];
/**
* 使用webworker开启一个线程池 执行复杂运算
* real parallel
* @param list 任务列表
* @param max 最大并发数 默认为2 最好和cpu数量一致 不要设置过多
* @returns 返回一个promise
*/
export const pool = async (list: PoolList, max = 1) => {
if (!blob) {
blob = new Blob([workerStr], { type: 'text/plain' });
}
if (!url) {
url = URL.createObjectURL(blob);
}
const store: { [key: string]: TaskRes } = {}
let _resolve: Resolve;
const p = new Promise((resolve) => {
_resolve = resolve;
})
let doneCount = 0;
let index = 0;
const runner = (curWorker: Worker) => {
const curIndex = index++;
if (!curWorker || !list[curIndex]) {
return;
}
const { key, task } = list[curIndex];
curWorker.postMessage({
key,
task: `${task}`
});
curWorker.onmessage = function (event) {
store[event.data.key] = event.data;
doneCount++;
if (doneCount === list.length) {
_resolve(store)
return;
}
runner(curWorker)
}
curWorker.onerror = function (event) {
console.dir('test error', event)
}
}
for (let i = 0; i < max; i++) {
let worker = threadPool[i];
if (!worker) {
worker = new Worker(url)
threadPool[i] = worker;
}
runner(worker)
}
return p
}
/**
* clear thread pool
*/
export const clearPool = () => {
threadPool.forEach(worker => {
worker.terminate()
})
threadPool = [];
}
export default pool;
线程池源码
运行结果 见上文截图。
转载自:https://juejin.cn/post/7410703833326829587