FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
362 stars 39 forks source link

一次失败的用web worker提升速度的实践 #181

Open FrankKai opened 4 years ago

FrankKai commented 4 years ago

web worker解决什么问题?

由于JavaScript是单线程的,当一段js代码执行耗时过久时,会有阻塞的情况出现。 web worker就是解决阻塞的一种方式。它开启了一个一个worker thread,将复杂计算放在worker线程中运行,将同步代码转换为异步代码。

可以看这个例子。

main thread同步方式:

      const btn = document.querySelector('button');
      btn.addEventListener('click', () => {
        let myDate;
        for(let i = 0; i < 10000000; i++) {
          let date = new Date();
          myDate = date
        }

        console.log(myDate);

        let pElem = document.createElement('p');
        pElem.textContent = 'This is a newly-added paragraph.';
        document.body.appendChild(pElem);
      });

main thread和worker thread异步方式:

      const btn = document.querySelector('button');
      const worker = new Worker('worker.js');

      btn.addEventListener('click', () => {
        worker.postMessage('Go!');

        let pElem = document.createElement('p');
        pElem.textContent = 'This is a newly-added paragraph.';
        document.body.appendChild(pElem);
      });

      worker.onmessage = function(e) {
        console.log(e.data);
      }

worker.js

onmessage = function() {
  let myDate;
  for(let i = 0; i < 10000000; i++) {
    let date = new Date();
    myDate = date
  }
  postMessage(myDate);
}

vue-cli3 项目配置( webpack4 worker-loader2 )

// 安装work-loader2.0.0
yarn add -D worker-loader
// vue.config.js
chainWebpack: config => {
  // 配置worker-loader
  config.module
    .rule("web worker")
    .test(/\.worker\.js$/)
    .use("worker-loader")
    .loader("worker-loader")
    .tap(() => {
      // 这一行配置非常重要
      return { inline: true };
    })
    .end();
}

手上项目测试demo( webpack3 worker-loader1 )

安装和配置

// 安装work-loader1.1.1
yarn add -D worker-loader@1.1.1

这是因为worker-loader2.0.0移除了对webpack3的支持,而work-loader1.1.1既支持w3也支持w4。可查看worker-loader release日志

// webpack.base.conf.js
module: {
    rules: [{
      test: /\.worker\.js$/,
      use: {
        loader: 'worker-loader',
        options: {
          inline: true,
        },
      },
    }]
}

demo测试

worker线程

// src/workers/test.worker.js
onmessage = function(evt) {
  // 工作线程收到主线程的消息
  console.log("worker thread :", evt); // {data:{msg:”Hello worker thread.“}}
  // 工作线程向主线程发送消息
  postMessage({
    msg: "Hello main thread."
  });
};

main线程

// src/pages/worker.vue
<template>
  <div>Main thread</div>
</template>

<script>
import TestWorker from "../workers/test.worker.js";

export default {
  name: "worker",
  created() {
    const worker = new TestWorker();
   // 主线程向工作线程发送消息
    worker.postMessage({ msg: "Hello worker thread." });
   // 主线程接收到工作线程的消息
    worker.onmessage = function(event) {
      console.log("main thread", event); // {data:{msg:"Hello main thread."}}
    };
  }
};
</script>

手上项目web worker 实践(迁移热力图计算逻辑)

安装和配置与上面一致。

工作线程

// src/workers/heat.worker.js
function cellClassNameSet() { ... }
function extractMinMax() { ... }
onmessage = (event) => {
  const { type, metaData } = event.data;
  // 生成热力样式
  if (type === 'HEAT_GENERATE') {
    const sweetData = cellClassNameSet(metaData);
    postMessage({ status: 'HEAT_GENERATE_SUCCESS', sweetData });
  }
  // 生成最大最小值
  if (type === 'MAX_MIN_EVALUATE') {
    const sweetData = extractMinMax(metaData);
    postMessage({ status: 'MAX_MIN_EVALUATE_SUCCESS', sweetData });
  }
};

主线程

// src/pages/heat.vue
<scripts>
import HeatWorker from '../workers/heat.worker';
// 引入web worker,尝试提升计算性能
let worker = new HeatWorker();

created(){ 
    this.heatWebWorkerStart() 
},
beforeDestory() { 
    worker.terminate();worker = null;
},
methods: {
    heatWebWorkerStart() {
      worker.onmessage = (event) => {
        const { status, sweetData } = event.data;
        if (status === 'HEAT_GENERATE_SUCCESS') {
          // 设置primitiveIndex用于显示隐藏条目
         // ...do something with sweetData
          this.getSliderMinMax();
        }
        if (status === 'MAX_MIN_EVALUATE_SUCCESS') {
           // ...do something with sweetData
          // 等热力计算和最大最小值计算完成,刷新监听器
        }
      };
    },
    // 构建出热力图
    generateBeautyCell() {
      worker.postMessage({ type: 'HEAT_GENERATE', metaData });
    },
    // 计算范围最大最小值
    getSliderMinMax() {
      worker.postMessage({ type: 'MAX_MIN_EVALUATE', metaData });
    },
}
</scripts>

但是添加工作线程之后,自身体感功能有如下变化:

经过性能分析工具分析之后,有如下分析:

总结与思考

失败就真的没有收获吗?

做了一次web worker在项目上的尝试,虽然失败但也是经验的积累。 学到了如何配置webpack的worker-loader;主线程与工作线程之间如何进行数据传递;主线程在控制台的sources面板会开启一个独立的工作线程。

web worker应用场景探索

web worker的应用场景据说包括光线追踪(Ray Tracing),加密,数据预获取,Progressive Web App,拼写检查。

其他选项与业务场景脱离,但是数据预处理方面在项目中是可以实践的。

引用一段How JavaScript works: The building blocks of Web Workers + 5 cases when you should use them 中的话

数据预获取: 为优化你的网站或 web 应用的数据加载时长,你可以使用 Web Worker 预先获取一些数据,存储起来以备后续使用。Web Worker 在这里发挥着重要作用,因为它绝不会影响应用的 UI 体验,若不使用 Web Worker 情况会变得异常糟糕。

数据预处理方面我想可以结合IndexedDB,localStorage等前端存储(可以用到localForage去择优选择前端存储)去做一些数据的预加载,比如图片,音频,视频等静态资源的预加载,通过工作线程默默地用户无感知地将资源缓存到本地,提升用户体验。其实主要是预先存储一些大块的内容,暂时不用但是后续会用到的。

有机会可以做一些实践尝试。