老问题,让我们重温一下! 准备肾上腺素
我一直在研究使用 Web Worker 来隔离 3rd 方插件,因为 Web Worker 无法访问主机页面。我会用你的方法帮助你,我相信你现在已经解决了,但这是针对 internetz 的。然后我会提供一些我研究的相关信息。
免责声明:在我使用您的代码的示例中,我已经修改并清理了代码以提供不带 jQuery 的完整源代码,以便您和其他人可以轻松运行它。我还添加了一个计时器,以毫秒为单位提醒执行代码的时间。
在所有示例中,我们引用了以下genericWorker.js 文件。
genericWorker.js
self.onmessage = function(event) {
self.postMessage(event.data);
};
方法一(线性执行)
您的第一种方法几乎可以正常工作。它仍然失败的原因是,一旦你完成了他们,你就不会删除任何工人。这意味着会发生相同的结果(崩溃),只是速度较慢。您需要解决的只是在创建新工作人员之前添加worker.terminate(); 以从内存中删除旧工作人员。请注意,这将导致应用程序的运行速度大大变慢,因为必须先创建、运行和销毁每个工作程序,然后才能运行下一个工作程序。
Linear.html
<!DOCTYPE html>
<html>
<head>
<title>Linear</title>
</head>
<body>
<pre id="debug"></pre>
<script type="text/javascript">
var debug = document.getElementById('debug');
var totalWorkers = 250;
var index = 0;
var start = (new Date).getTime();
function createWorker() {
var worker = new Worker('genericWorker.js');
worker.onmessage = function(event) {
debug.appendChild(document.createTextNode('worker.onmessage i = ' + event.data + '\n'));
worker.terminate();
if (index < totalWorkers) createWorker(index);
else alert((new Date).getTime() - start);
};
worker.postMessage(index++); // start the worker.
}
createWorker();
</script>
</body>
<html>
方法二(线程池)
使用线程池应该会大大提高运行速度。与其使用一些具有复杂术语的库,不如简化它。所有线程池意味着有一定数量的工人同时运行。我们实际上可以从线性示例中修改几行代码,以获得多线程示例。下面的代码会找出你有多少核(如果你的浏览器支持的话),或者默认为 4。我发现这段代码在我的机器上运行的速度比原来的 8 核快 6 倍。
ThreadPool.html
<!DOCTYPE html>
<html>
<head>
<title>Thread Pool</title>
</head>
<body>
<pre id="debug"></pre>
<script type="text/javascript">
var debug = document.getElementById('debug');
var maxWorkers = navigator.hardwareConcurrency || 4;
var totalWorkers = 250;
var index = 0;
var start = (new Date).getTime();
function createWorker() {
var worker = new Worker('genericWorker.js');
worker.onmessage = function(event) {
debug.appendChild(document.createTextNode('worker.onmessage i = ' + event.data + '\n'));
worker.terminate();
if (index < totalWorkers) createWorker();
else if(--maxWorkers === 0) alert((new Date).getTime() - start);
};
worker.postMessage(index++); // start the worker.
}
for(var i = 0; i < maxWorkers; i++) createWorker();
</script>
</body>
<html>
其他方法
方法三(单人重复任务)
在您的示例中,您一遍又一遍地使用同一个工人。我知道您正在简化一个可能更复杂的用例,但某些查看者会看到这一点并在他们可能只使用一个工作人员完成所有任务时应用此方法。
基本上,我们将实例化一个工作线程,发送数据,等待数据,然后重复发送/等待步骤,直到所有数据都处理完毕。
在我的计算机上,它的运行速度大约是线程池的两倍。这真的让我很惊讶。我认为线程池的开销会导致它比速度慢 1/2。
RepeatedWorker.html
<!DOCTYPE html>
<html>
<head>
<title>Repeated Worker</title>
</head>
<body>
<pre id="debug"></pre>
<script type="text/javascript">
var debug = document.getElementById('debug');
var totalWorkers = 250;
var index = 0;
var start = (new Date).getTime();
var worker = new Worker('genericWorker.js');
function runWorker() {
worker.onmessage = function(event) {
debug.appendChild(document.createTextNode('worker.onmessage i = ' + event.data + '\n'));
if (index < totalWorkers) runWorker();
else {
alert((new Date).getTime() - start);
worker.terminate();
}
};
worker.postMessage(index++); // start the worker.
}
runWorker();
</script>
</body>
<html>
方法 4(带线程池的重复工作者)
现在,如果我们将前面的方法与线程池方法结合起来呢?从理论上讲,它应该比以前运行得更快。有趣的是,它的运行速度与我机器上的前一个速度几乎相同。
也许这是每次调用工作人员引用时发送的额外开销。也许是在执行期间终止了额外的工作人员(在我们得到时间之前只有一名工作人员不会被终止)。谁知道。找出这一点是另一次的工作。
RepeatedThreadPool.html
<!DOCTYPE html>
<html>
<head>
<title>Repeated Thread Pool</title>
</head>
<body>
<pre id="debug"></pre>
<script type="text/javascript">
var debug = document.getElementById('debug');
var maxWorkers = navigator.hardwareConcurrency || 4;
var totalWorkers = 250;
var index = 0;
var start = (new Date).getTime();
function runWorker(worker) {
worker.onmessage = function(event) {
debug.appendChild(document.createTextNode('worker.onmessage i = ' + event.data + '\n'));
if (index < totalWorkers) runWorker(worker);
else {
if(--maxWorkers === 0) alert((new Date).getTime() - start);
worker.terminate();
}
};
worker.postMessage(index++); // start the worker.
}
for(var i = 0; i < maxWorkers; i++) runWorker(new Worker('genericWorker.js'));
</script>
</body>
<html>
现在是一些现实世界的东西
还记得我说过我使用工人在我的代码中实现 3rd 方插件吗?这些插件有一个需要跟踪的状态。我可以启动插件并希望它们不会加载太多导致应用程序崩溃,或我可以在我的主线程中跟踪插件状态并将该状态发送回插件,如果插件需要重新加载。我更喜欢第二个。
我已经写了几个有状态、无状态和状态恢复工作人员的例子,但我会避免你的痛苦,只是做一些简短的解释和一些简短的 sn-ps。
首先,一个简单的有状态工作者如下所示:
StatefulWorker.js
var i = 0;
self.onmessage = function(e) {
switch(e.data) {
case 'increment':
self.postMessage(++i);
break;
case 'decrement':
self.postMessage(--i);
break;
}
};
它根据接收到的消息执行一些操作并在内部保存数据。这很棒。它允许 mah 插件开发人员完全控制他们的插件。主应用程序实例化他们的插件一次,然后将发送消息让他们执行一些操作。
当我们想一次加载多个插件时,问题就来了。我们不能那样做,那么我们能做什么?
让我们考虑几个解决方案。
解决方案 1(无状态)
让我们让这些插件无状态。本质上,每次我们想让插件做某事时,我们的应用程序都应该实例化插件,然后根据它的旧状态向它发送数据。
发送的数据
{
action: 'increment',
value: 7
}
StatelessWorker.js
self.onmessage = function(e) {
switch(e.data.action) {
case 'increment':
e.data.value++;
break;
case 'decrement':
e.data.value--;
break;
}
self.postMessage({
value: e.data.value,
i: e.data.i
});
};
这可行,但如果我们要处理大量数据,这似乎不是一个完美的解决方案。另一个类似的解决方案可能是每个插件都有几个较小的工作人员,并且只向每个插件发送少量数据,但我也对此感到不安。
解决方案 2(状态恢复)
如果我们尝试尽可能长时间地将工作人员保留在内存中,但如果我们确实丢失了它,我们可以恢复它的状态吗?我们可以使用某种调度程序来查看用户一直在使用哪些插件(也许还有一些奇特的算法来猜测用户将来会使用什么)并将它们保存在内存中。
关于这一点很酷的部分是我们不再关注每个核心一个工人。由于大多数时候worker处于活动状态是空闲的,我们只需要担心它占用的内存。对于大量工人(10 到 20 人左右)来说,这根本不算什么。我们可以保持主要插件的加载,而那些不经常使用的插件会根据需要被关闭。 所有插件仍然需要某种状态恢复。
让我们使用以下 worker 并假设我们发送“增量”、“减量”或包含它应该处于的状态的整数。
StateRestoreWorker.js
var i = 0;
self.onmessage = function(e) {
switch(e.data) {
case 'increment':
self.postMessage(++i);
break;
case 'decrement':
self.postMessage(--i);
break;
default:
i = e.data;
}
};
这些都是非常简单的例子,但我希望我能帮助理解有效地使用多个工人的方法!我很可能会为这些东西编写一个调度器和优化器,但谁知道我什么时候能做到这一点。
祝你好运,编码愉快!