likes
comments
collection
share

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

作者站长头像
站长
· 阅读数 12

大家好,我是梦兽。一个技术分享宅男。欢迎大家订阅我的b站频道。 傻梦兽的个人空间_哔哩哔哩_bilibili

最近梦兽,我在对接一个项目。这个项目的"前任"使用了webworker。但是它使用的这个webworker有点复杂让我们团队几个人看也看了很久,是以面向对象的思路去做,但很多地方为了封装而封装,然让阅读起来非常困难。

我们想看看他原来是怎么使用的web woker。

注意: 如果你的项目没使用webpack-loacker和vite的话,我建议你不用使用woker。因为web woker需要单独编译文件!

ThreadPool 部分重构

先看看"前任"写的Manage,我估计它是想写线程池的。但是在Manage之后需要执行init。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

在init方法里面做了很多注册方法.这样导致这个线程池使用起来很纯!

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

看到这里我已经知道了,这个不是woker,只能说他使用了一个woker,然后处理了websocket的消息。

所以导致他需要在使用层,需要使用一个map使用时间戳来读取,所以需要把woker的onmessage写在调用的地方,进行set和get进行消息的消费,然后再执行回调函数,这样做实在是太蠢,而且增加了阅读的成本。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

如果我们来封装的话,我们可以先写真正的写一个线程池thead_pool.ts,内部就能自己消费message即可。

type ThreadPoolQueue = [(value: any) => void, (reason: any) => void, any[]];

export class ThreadPool {

  private readonly workers: Set<Worker>;
  private readonly freeWorkers: Worker[];
  private readonly queue: ThreadPoolQueue[] = [];

  /**
  * 创建一个线程池
  * @param fn 线程池要执行的函数,它不可带有任何闭包变量,且只能使用有限的函数。
  * 详见 https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope
  * @param size 线程个数(最大并发数,必须为大于 0 的整数)
  */

  constructor(worker: Worker, size = navigator.hardwareConcurrency - 1) {
    if (size < 1) throw new RangeError("size must grater than 0");

    this.freeWorkers = Array.from({ length: size }, () => worker);
    this.workers = new Set(this.freeWorkers);

  }

  /**
    * 当有线程空余时,将参数转发至线程,开始执行。
    * 当没有线程空余时,将参数追加至调度队列,等待其他线程空余。
    * @param args 传入线程函数的参数。注意它们会以结构化克隆的方式传入(类似深拷贝),而非通常的引用传值。
    * https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
    * @returns Promise,当线程函数执行完毕后 resolve 其返回值
    */
  dispatch(...args: any[]) {
    return new Promise<any>((resolve, reject) => this.start(resolve, reject, args));
  }

  /**
  * 立即结束所有线程,释放资源。
  * 注意:本函数会强制停止正在运行中的线程,并 reject 所有等待中的 promise
  */
  dispose() {
   
  }

  /**
   * 获得当前空闲的线程个数
   */
  getFreeWorkerCount() {
    return this.freeWorkers.length;
  }


  /**
   * 获得当前在队列中等待的事件个数
   */
  getWaitingEventCount() {
  }

  /// 私有方法
  private onFinish(worker: Worker) {
    worker.onmessage = null as any;
    worker.onerror = null as any;
    this.freeWorkers.push(worker);

    if (this.queue.length) {
      this.start(...this.queue.shift() as ThreadPoolQueue);
    }
  }

  private start(resolve: (value: any) => void, reject: (reason: any) => void, args: any[]) {

    if (this.freeWorkers.length) {
      const worker = this.freeWorkers.pop() as Worker;
      worker.onmessage = e => {
        this.onFinish(worker);
        resolve(e.data);
      };
      worker.onerror = e => {
        this.onFinish(worker);
        reject(e.error);
      };
      worker.postMessage(args);
    } else {
      this.queue.push([resolve, reject, args]);
    }
  }


}

当我我们有了以上的代码之后,我们就不需要把我们的woker写在主线程文件一起阅读。 那我们要如何是使用呢? 也是需要写一份woker文件。 然后你想使用这个woker 只需要。

    this.write_pool = new ThreadPool( 
        // 线程池要执行的函数,它不可带有任何闭包变量,且只能使用有限的函数。 [DedicatedWorkerGlobalScope - Web API 接口参考 | MDN (mozilla.org)](https://developer.mozilla.org/zh-CN/docs/Web/API/DedicatedWorkerGlobalScope) 
            new WebWoker(), 
            // 协程数创建多少个worker进行通信 
            2); 
            
        this.write_pool.dispatch(
        // 使用worker 处理的数据 
        this.rpcdrive.sendMessage(data) ).then(res => { 
        // 当线程函数执行完毕后 resolve 其返回值 
        console.log(res);
        })

这个时候就已经把thread pool进行抽离。你想写的任何线程都可以直接类似Java的ThreadPool那种进行start。提高了我们的可读性你想知道做了什么事情就直接去看Worker里面看就好了。

我们看一下重构后的效果,我们现在可以在开不同的线程进行读写,而不是想之前读写都在一个线程里面进行。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

消息序列化和放序列号 部分重构

我们再看看对blod二进制进行的操作,存在很大一部分为了封装而封装。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

由于没有上面所说的自己消费queue,还需要一个controller进行管理消息的创建然后转换数据blob给服务器。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

当我看懂了这部分代码后,他做了这么多事情,其实实际两个文件就可以做完。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

如何让你使用的web woker使用起来想java线程池一样简单,还有一些重构的思路。

总结

代码是写起来跑的,还有另一面是让人更好的理解。不要为了面向对象封装,过度的封装并不是一件很好的事情。