- thread pool pattern 是后端多线程语言中的一个概念,web worker 技术和真正的后端技术略有差别,因此不能完全按照行业规范去实现。
- 线程池模式是一个大的概念,有很多细节,自己实现可能漏洞比较多,github 上有很多实现,但都是特别简单,功能覆盖率也不高,可以自行搜索。
- 调研比较成熟的第三方类库,筛选出两个 workerpool 和 threads.js,下面是部分信息对比表:
综合对比,threads.js 是最优的,threads.js 官方并没有很好的示例,这里有一个threads.js 实现示例可以参考。
- 贴一些自实现代码,作为参考:
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;