WEB-WORKER 实用场景
- 密集型计算任务 (递归、计算、数据处理)
- 预处理请求
- 高频请求(心跳检测、用户行为捕捉等)
内部实现队列机制
-
初衷
-
一般做用户行为分析都会埋入锚点,当用户点击后通过AJAX向后台发送用户的操作、时间、功能等信息
-
在WEB-WORKER之前通过主线程发送高频的数据势必会占用主线程、并发请求上限。这就会造成请求的等待或者阻塞。这种情况下会通过跨域将数据发送到非同源后端服务,规避同源策略下的请求数量上限。但是主线程仍然会受到一定的影响
-
所以这时候就考虑使用WEB-WORKER。仍然是规避同源策略。由于WEB-WORKER独立于主线程所以主线程将数据交由WORKER处理即可。不再需要支付额外的AJAX开销
-
但是这就带来一个问题。当大量的WORK发送大量的AJAX那WORKER的请求就会等待,这样效率不高。所以就考虑在WORKER内部实现一个任务队列,按照压入的顺序执行,这样就可以使性能得到保证。
-
-
想要实现队列,必须要有对应的容器。在后端语言中有QUEUE可以直接使用。JAVASCRIPT中没有,但是我们有数组
-
但是需要注意的是数组提供的POP函数是删除并返回数组的最后一个元素。队列是遵循先进先出。所以不能使用PUSH数组尾端添加元素,需要使用UNSHIFT向数组头部添加数据
-
-
内部执行机制如下图
- 1.通过监听接收主线程发送来的消息
- 2.直接将消息压入队列中
- 3.通过标记判断队列是否空闲
- 4.队列空闲执行逻辑方法,队列非空闲则不做任何操作
- 5.执行内部逻辑,执行完毕后返回数据,再次执行上一步。直到队列空闲
- 6.更新队列空闲标记,等待下次消息到达
-
-
这样保证了任务的顺序执行,同时也解决了高并发请求等待的问题
-
-
关于线程池
- 初衷
- 在使用WEB-WORKER的过程中,一般要手动开启WEB-WORKER。然后向WORKER发送消息执行逻辑接收返回参数。
- 出于平衡资源与不浪费性能的考虑,将WORKER的能力集中化。也就是将代码都编写在一个WORKER中。通过轮询的方式均衡每个WORKER。达到很好的复用的效果
- 所以这就有了线程池的概念。线程池一次开启多个子线程提供使用。内部提供集中负载算法,均衡子线程的承载。常见的有轮询,加权轮询,最佳可用,随机等。
- 线程池提供统一的执行入口。子线程通过参数判断执行的任务类型做对应逻辑。通过数组装载子线程实例。Map做子线程任务索引,int类型的轮询计数
- 轮询 内部持有递增变量通过 变量%数组长度 (
loop % pool.length) - 最佳可用 计算线程的等待数 * 平均耗时 选择结果是最小值的子线程 (
min(waot * rtt))
- 轮询 内部持有递增变量通过 变量%数组长度 (
性能分析
- 火狐性能分析
- 单线程线性队列
- 线程池并发任务
相关代码
/**
* 多线程工具
*/
thread: {
/** 轮询计数 */
_loop: 0,
/** 线程池 {id:String, wait:int, thraad: webWorker} */
_pool: [],
/** 任务Map, key为每个任务的唯一ID, value存储任务的回调函数等信息 */
taskMap: new Map(),
/** 均衡策略 */
BALANCE_RULE: {
/** 轮询 */
ROUND_ROBIN: 0,
/** 最佳可用, 选择等待排队最短的队列 */
BEST_AVAILABLE: 1,
/** 随机, 应该不太会用到 */
RANDOM: 99
},
/** 均衡算法 */
BALANCE_ALGORITHM: {
/** @return {number} 轮询下标 */
ROUND_ROBIN: function () {
return this._loop++ % this._pool.length;
},
/** @return {number} 等待最少的任务下标 // TODO SHADOW 待优化,子线程RTT由哪段处理 */
BEST_AVAILABLE: function () {
var min, index = 0;
this._pool.forEach(function (thread, i) {
var wait = thread.wait;
if (typeof min === "undefined") {
min = wait;
index = i;
} else {
if (wait < min) {
min = wait;
index = i;
}
}
});
return index;
},
/** @return {number} 随机下标 */
RANDOM: function () {
return Math.round(Math.random() * (this._pool.length - 1))
}
},
/** 负载均衡 */
balanceRule: 0,
/**
* 默认回调函数
* @param event 线程回到
* @private
*/
_defaultCallback: function (event) {
var result = event.data;
// 校验返回数据是否存在
if (result.id) {
this._updateThreadPool(result);
this._reactive(result)
} else {
throw new Error("线程响应没有ID");
}
},
/**
* 响应方法
* 通过ID 获取任务Map中的回调,然后调用
* @param result 线程返回结果
* @private
*/
_reactive: function (result) {
var task = this.taskMap.get(result.id);
if (task && task.callback) {
task.callback.call(undefined, result.body)
}
this.taskMap.delete(result.id);
},
/**
* 更新线程池信息
* @param task 线程返回结果
* @private
*/
_updateThreadPool: function (task) {
this._pool.forEach(function (thread) {
if (thread.id === task.threadId) {
thread.wait = task.wait
}
})
},
/**
* 初始化 - 单个线程
* @param src 脚本路径
* @param autoClose 是否自动关闭 默认true
* @returns {Worker}
*/
single: function (src, autoClose) {
if (!Worker) {
throw new Error("浏览器不支持Web Worker")
}
var scriptRegexp = /(\S*).js$/g;
// 校验js文件的正确性
if (!src || !scriptRegexp.test(src)) {
throw new Error("脚本文件路径不正确!");
}
// 校验文件是否存在
autoClose = autoClose !== false;
var thread = new Worker(base + src);
var the = this;
thread.onmessage = function (event) {
the._defaultCallback(event);
if (autoClose) {
the.terminate(thread);
}
};
thread.onerror = function (ev) {
throw new Error("线程创建失败!");
};
return thread;
},
/**
* 初始化 - 线程池
* @param src 脚本路径
*/
pool: function (src) {
var poolSize = navigator.hardwareConcurrency && navigator.hardwareConcurrency > 2 ? navigator.hardwareConcurrency : 2;
var index = 0;
while (index < poolSize) {
this._pool.push({
id: this.getUniqueID("THREAD_"),
wait: 0,
worker: this.single(src, false),
closed: false
});
index++;
}
},
/**
* 执行任务,调用Worker.postMessage 向worker线程发送数据
* @param options 参数
* @param callback 回调
* @param thread 线程
*/
run: function (options, callback, thread) {
if (!thread) {
// 如果没有传入线程,向线程池获取资源
thread = this.getWorker();
if (!thread) {
throw new Error("没有传入执行线程并且也没有在线程池中找到可用线程!")
}
}
if (thread.closed) {
throw new Error("该线程已关闭/释放资源!")
}
if (!options) {
throw new Error("没有接收到任何参数!");
}
var id = this.getUniqueID("TASK_");
var message = {id: id, body: options, threadId: thread.id};
var task = {id: id, body: options, callback: callback};
this.taskMap.set(task.id, task);
thread instanceof Worker ? thread.postMessage(message) : thread.worker.postMessage(message);
},
/**
* 获取线程池资源
* 线程池使用机制,轮询,空闲优先,加权算法
* @returns {Worker}
*/
getWorker: function () {
var index;
switch(this.balanceRule) {
case this.BALANCE_RULE.ROUND_ROBIN: index = this.BALANCE_ALGORITHM.ROUND_ROBIN.call(this);break;
case this.BALANCE_RULE.BEST_AVAILABLE: index = this.BALANCE_ALGORITHM.BEST_AVAILABLE.call(this);break;
case this.BALANCE_RULE.RANDOM: index = this.BALANCE_ALGORITHM.RANDOM.call(this);break;
}
return this._pool[index];
},
/**
* 关闭线程,调用Worker.terminate
* @param thread 线程对象
*/
terminate: function (thread) {
if (thread instanceof Worker) {
thread.terminate();
} else {
thread.worker.terminate();
}
thread.closed = true;
},
/**
* 清除线程池
*/
destroy: function () {
if(this._pool.length > 0) {
this._pool.forEach(this.terminate);
}
},
/**
* 唯一ID
*/
getUniqueID: function (prefix) {
return (typeof prefix == 'undefined' || "" == prefix) ? 'prefix_' + Math.floor(Math.random() * (new Date()).getTime()) : prefix + Math.floor(Math.random() * (new Date()).getTime());
}
}
/**
* 监听事件
* @param event
*/
self.addEventListener("message", event => {
RequestQueue.addTask(event.data);
});
/**
* 请求队列
* 1.脚本加载完成后启动
* 2.接收数据后做基础校验,校验通过后压入队列.
*
*/
const RequestQueue = function () {
/** 内部队列*/
const queue = [];
/** 队列空闲标志 */
let ready = true;
/**
* 添加任务
* 如果队列空闲将任务压入队列,同时执行
* 如果没有空闲则只将任务压入
* @param data 数据
*/
let addTask = function (data) {
if (_validate(data)) {
if(ready){
queue.unshift(data);
_run();
}else {
queue.unshift(data);
}
}
};
/**
* 校验
* @param data 数据
* @returns {boolean} undefined/true
* @private
*/
let _validate = function (data) {
if (!data.body) {
throw new Error("传输数据不存在!")
}
return true;
};
/**
* 运行任务
* 1.将队列中的数据弹出
* 2.如果有数据执行_execute执行
* @private
*/
let _run = function () {
let next = queue.pop();
if (next) {
_execute(next);
ready = false
}else {
ready = true
}
};
/**
* 执行
* @param next 下一个数据
* @private
*/
let _execute = function (next) {
const body = next.body;
try {
// dothing
_run();
postMessage({...});
} catch (e) {
_run();
postMessage({...});
}
};
return {
addTask: addTask,
}
}();