Web Worker 线程池模式方案

2,821 阅读4分钟
  1. thread pool pattern 是后端多线程语言中的一个概念,web worker 技术和真正的后端技术略有差别,因此不能完全按照行业规范去实现。
  2. 线程池模式是一个大的概念,有很多细节,自己实现可能漏洞比较多,github 上有很多实现,但都是特别简单,功能覆盖率也不高,可以自行搜索。
  3. 调研比较成熟的第三方类库,筛选出两个 workerpoolthreads.js,下面是部分信息对比表:

image.png

image.png

image.png

综合对比,threads.js 是最优的,threads.js 官方并没有很好的示例,这里有一个threads.js 实现示例可以参考。

  1. 贴一些自实现代码,作为参考:

  var ThreadPool = function () {
  //WebWorker ThreadPool
  var WWTP = new Object();
  /**
 * 初始化
 * @param {*} jsPath 子线程js路径
 * @param {*} size 线程池线程总数,需大于0
 */
  WWTP.init = function (jsPath, size) {
      this.queue = [];
      this.queueWithCallback = [];
      if (isNaN(size)) size = navigator.hardwareConcurrency - 1;
      if (size < 1) throw new RangeError('size must greater than 0');

      this.freeWorkers = Array.from({
          length: size
      }, () => new Worker(jsPath));
      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 其返回值
 */
  WWTP.dispatch = function (args) {
      return new Promise((resolve, reject) =>
          start(resolve, reject, args)
      );
  }


  WWTP.dispatchWithCallback = function (args, callback, error) {
      return new Promise((resolve, reject) =>
          startWithCallback(callback, error, args)
      );
  }
  /**
 * 立即结束所有线程,释放资源。
 * 注意:本函数会强制停止正在运行中的线程,并 reject 所有等待中的 promise
 */
  WWTP.dispose = function () {
      this.freeWorkers.forEach((x) => {
          this.workers.delete(x);
          x.terminate();
      });
      this.queue.forEach(([, reject]) => {
          reject(new TypeError('threadpool disposed'));
      });
      this.queue.length = 0;
      this.workers.forEach((x) => {
          x.terminate();
          x.onerror(new ErrorEvent('error', {
              error: new TypeError('threadpool disposed')
          }));
      });
      this.workers.clear();
      this.freeWorkers.length = 0;
  }
  /**
 * 获得当前空闲的线程个数
 */
  WWTP.getFreeWorkerCount = function () {
      return this.freeWorkers.length;
  }
  /**
 * 获得当前运行中的线程个数
 */
  WWTP.getRunningWorkerCount = function () {
      return this.workers.size - this.freeWorkers.length;
  }
  /**
 * 获得当前在队列中等待的事件个数
 */
  WWTP.getWaitingEventCount = function () {
      return this.queue.length;
  }

  /// 私有方法
  function onFinish(worker) {
      worker.onmessage = null;
      worker.onerror = null;
      WWTP.freeWorkers.push(worker);
      if (WWTP.queue.length > 0) {
          start(...WWTP.queue.shift());
      }
  }


  function start(resolve, reject, args) {
      if (WWTP.freeWorkers.length > 0) {
          const worker = WWTP.freeWorkers.pop();
          worker.onmessage = e => {
              onFinish(worker);
              if (typeof resolve == "function") resolve(e.data);
          };
          worker.onerror = e => {
              onFinish(worker);
              if (typeof reject == "function") reject(e.error);
          };
          worker.postMessage(args);
      } else {
          WWTP.queue.push([resolve, reject, args]);
      }
  }


  /**
 * 需主动结束worker,置为空闲态
 * @param {*} worker
 */
  WWTP.onFinishWithCallback = function (worker) {
      worker.onmessage = null;
      worker.onerror = null;
      WWTP.freeWorkers.push(worker);
      if (WWTP.queueWithCallback.length > 0) {
          startWithCallback(...WWTP.queueWithCallback.shift());
      }
  }


  function startWithCallback(callback, error, args) {
      if (WWTP.freeWorkers.length > 0) {
          const worker = WWTP.freeWorkers.pop();
          worker.onmessage = e => {
              // WWTP.onFinishWithCallback(worker);
              if (typeof callback == "function") callback(e.data, worker);
          };
          worker.onerror = e => {
              // WWTP.onFinishWithCallback(worker);
              if (typeof error == "function") error(e.error, worker);
          };
          worker.postMessage(args);
      } else {
          WWTP.queueWithCallback.push([callback, error, args]);
      }
  }


  return WWTP;
};
let _noop = function() {};
/**
 * 队列
 */
class Queue extends Set {
    constructor(iterable) {
        super(iterable);
    }

    /**
     * 添加一个元素到队列尾部
     */
    push(el) {
        this.add(el);
    }

/**
 * 移除并返回队列头部的元素
 */
    put() {
        let el;
        for (el of this) { break; }
        this.delete(el);
        
        return el;
    }
}


 /**
 * worker 代理
 */
class WorkerAgent {
    constructor(worker) {
        this._worker = worker;
        this._eventDeregistrations = [];
        this._isDestroy = false;
    }

    get onmessage() {
        return this._worker && this._worker.onmessage;
    }

    set onmessage(fun) {
        this._isDestroy === false && this._worker.onmessage = fun;
    }

    get onerror() {
        return this._worker && this._worker.onerror;
    }

    set onerror(fun) {
        this._isDestroy === false && this._worker.onerror = this.fun;
    }

    addEventListener(type, listener, useCapture, wantsUntrusted) {
        this._eventDeregistrations.push( () => {
            this._worker.removeEventListener(type, listener, useCapture);
        });

        this._worker.addEventListener(type, listener, useCapture, wantsUntrusted);
    }

    removeEventListener(type, listener, useCapture) {
        this._worker.removeEventListener(type, listener, useCapture);
    }

    cleanEventListener() {
        this._eventDeregistrations.forEach( (deregistration) => deregistration() );
    }

    /**
     * 解除代理跟 worker 的绑定
     */
    static destroy(agent) {
        let worker = agent._worker;
        // clean worker
        worker.onmessage = undefined;
        worker.onerror = undefined;
        agent.cleanEventListener();
        // clean agent
        agent._worker = undefined;
        agent._eventDeregistrations = undefined;
        agent.addEventListener = agent.removeEventListener = agent.cleanEventListener = noop;

        return worker;
    }
}
 /**
 * 线程池
 */
class WorkerPool {
    constructor(url, config) {
        config = config || {};
        // worker 脚本的 URL
        this.url = url;
        // 等待中的 worker 所在的线程池
        this.idlePool = new Queue();
        // 正在执行任务的 worker 所在线程池
        this.busyPool = new Queue();
        // 正在等待执行的任务队列
        this.queue = new Queue();
        // 在线程池所维护的 worker 的最大数量
        this.max = config.max || 10;
        // 当前线程池的长度
        this.length = 0;
    }

    /**

     * 登记以预约使用 worker;登记之后,当有一个空闲的 worker 可供使用时,会让该 worker 受理此次预约。
     *
     * @param {function} handler -
     *     当有一个空闲的 worker 可以用于执行此任务时,会调用该函数;
     *     在函数内,可以向 worker 发布消息,并监听 worker 所抛出的消息和异常;
     *     当使用完成后,需要调用 done() 函数通知线程池该任务执行完毕。
     *
     * @return {function} deregistration
     *
     *     若该登记还未被受理,调用该函数可以取消登记。
     *     若该登记已受理或受理完毕,调用该函数不会起任何作用。
     */

    register(handler) {
        let registerInfo = {
            handler: handler
        };
        this.queue.push(registerInfo);
        this._execute();

        return (()=> this._deregistration(registerInfo));
    }
    /**
     * 注销登记
     *
     * @param {number} registerInfo - 登记信息
     */
    _deregistration: function(registerInfo) {
        this.queue.remove(registerInfo);
    }

    /**
     * 使用一个空闲中的 worker 受理登记队列中的一个登记,若当前没有空闲的 worker,或登记队列为空,则不做任何操作。
     */
    _execute() {
        let worker = this._getWorker();
        if (!worker) {
            return;
        }
        let registerInfo = this.queue.put();
        if (!registerInfo) {
            return;
        }
        let agent = new WorkerAgent(worker);
        agent.release = () => {
            WorkerAgent.destroy(agent);
            this._releaseWorker(worker);
        };

        registerInfo.handler.call(agent, agent);
    }

    /**
     * 返回一个空闲中的 worker,同时将其从空闲池移到工作池中。
     */
    _getWorker() {
        if (!this.idlePool.length) {
            this._createWorker();
        }
        let worker = this.idlePool.put();
        if (worker) {
            this.idlePool.remove(worker);
            this.busyPool.push(worker);
        }

        return worker;
    }

    /**
     * 释放一个 worker,该操作会将 worker 从工作池移回到空闲池中,等待下次调用。
     */
    _releaseWorker(agent) {
        this.busyPool.remove(worker);
        this.idlePool.push(worker);
        this._execute();
    }

    /**
     * 创建一个 worker
     */
    _createWorker() {
        if (this.length >= this.max) {
            return null;
        }
        else {
            let worker = new Worker(this.url);
            this.idlePool.push(worker);
            this.length++;

            return worker;
        }
    }
}

export default WorkerPool;