切换语言为:繁体
H5的线程池性能优化

H5的线程池性能优化

  • 爱糖宝
  • 2024-09-05
  • 2048
  • 0
  • 0

先有问题再有答案

  1. js不是单线程嘛 为什么又说多线程?

  2. 如何理解H5的多线程?

  3. 如何理解h5的worker?

  4. web worker 有什么特点?

  5. 有什么使用场景?

  6. 有什么限制?

  7. 多线程间是如何通讯?

背景

JavaScript 是单线程运行的,这意味着同一个时间只能做一件事情。由于用户交互、网络请求、IO操作等可能会产生阻塞,所以 JavaScript 引入了异步机制和事件循环的概念。

单线程&异步

异步可以看做是单线程面对需要大量耗时操作时的一种解决方案,它将这些耗时操作异步处理,也就是挂起这些事件,进行下一步的操作。当异步事件处理完成后,再通过回调函数的方式进行处理。所以,异步并不是在单线程之外开辟了新的线程,而是通过事件挂起和回调函数的方式使得 JavaScript 引擎不需要阻塞等待,可以先处理其他任务。

单线程&事件循环

事件循环是 JavaScript 实现异步的一种方法,它可以解决 JavaScript 单线程同步执行带来的阻塞问题。事件循环的机制是,将所有的任务分为宏任务和微任务,JavaScript 引擎先执行一个宏任务,紧接着执行所有的微任务,然后再执行下一个宏任务。这种模式不断循环,称为事件循环

单线程&多线程

JavaScript是单线程的,这是因为JavaScript设计初衷是处理简单的DOM操作,复杂和耗时的操作可能导致页面阻塞,影响用户体验。

然而,随着Web应用变得越来越复杂,JavaScript也需要处理更多的复杂且计算密集型任务。为了解决这个问题,HTML5提出了Web Workers标准,允许JavaScript创建多个工作线程,这些工作线程在后台执行任务,不会影响主线程和用户界面的相应。

我们可以认为,JavaScript的主线程仍然是单线程的,但是通过Web Workers,JavaScript能够创建生成真正的操作系统级别的工作线程,实现多线程编程

web worker兼容性

H5的线程池性能优化

线上完全可用 无兼容性问题。

使用场景

cpu密集型的任务

如果有耗时任务 在主线程中长时间占用cpu,这样的计算可能会阻塞用户界面卡顿,导致无法响应用户的操作。

测试代码如下:

const test = () => {
  let count = 0;
  for (let i = 0; i < 1000000000; i++) {
    count = count + i;
  }
};
test();

运行在主线程的任务性能分析如图:
主线程耗时12s左右 这期间左侧列表是卡死 不能响应用户滚动的

  H5的线程池性能优化

运行在worker线程
性能分析如图-1
可以看到当选中主线程时 js执行仅3毫秒

H5的线程池性能优化

性能分析如图-2
当选中worker线程时 js执行耗时13s

H5的线程池性能优化

运行在worker中的整个过程 左侧列表是可以响应用户滚动的, 这就是多线程的魅力 开启了一个工作线程 分担了主线程的耗时任务 有效的避免了卡顿。

周期性后台任务

这部分一般不需要用户交互,但需要定时执行的任务。这类任务通常包括一些定时获取服务器数据、清理缓存、自动保存用户操作等。 例如高精准大定时器功能。

使用限制

同源限制:

Web Workers 只能加载来自同源的脚本。也就是说,你不能在你的网站上使用一个来自其他域的脚本,这是出于安全原因。

不能访问 DOM

Web Workers 不能访问页面的 DOM。这是因为 DOM 操作不能用于多线程环境。目的是防止出现两个线程对 DOM 进行修改而造成的数据不同步。

不能访问某些对象:

Web Workers 不能访问window对象、document对象、parent对象等, 即使Web Workers不能访问这些对象,它们仍然可以发起AJAX请求或使用 WebSockets 这类网络API。

使用方式

创建worker

const worker = new Worker('worker.js', {
  type: 'module',
  credentials: 'same-origin'
});

Worker构造函数接收两个参数:

  1. url: 这是一个字符串值,表示一个指向 JavaScript 文件的 URL。这个参数是必须的,它的值应该是一个相对于当前 HTML 文档的相对路径,或者是一个完整的 URL。

  2. 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;

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.