Web Worker限制数量

时间:2012-11-26 22:21:19

标签: javascript

问题

我发现浏览器可以生成的Web Workers数量有限制。

示例

主HTML / JavaScript

<script type="text/javascript">
$(document).ready(function(){
    var workers = new Array();
    var worker_index = 0;
    for (var i=0; i < 25; i++) {
        workers[worker_index] = new Worker('test.worker.js');
        workers[worker_index].onmessage = function(event) {
            $("#debug").append('worker.onmessage i = ' + event.data + "<br>");
        };
        workers[worker_index].postMessage(i); // start the worker.      

        worker_index++;
    }   
});
</head>
<body>
<div id="debug">
</div>

test.worker.js

self.onmessage = function(event) {
    var i = event.data; 

    self.postMessage(i);
};

使用Firefox(版本14.0.1,Windows 7)时,这将在容器中仅生成20个输出行。

问题

有解决方法吗?我能想到的唯一两个想法是:

1)雏菊链接网络工作者,即让每个网络工作者产生下一个

示例:

<script type="text/javascript">
$(document).ready(function(){
    createWorker(0);
});

function createWorker(i) {

    var worker = new Worker('test.worker.js');
    worker.onmessage = function(event) {
        var index = event.data;

        $("#debug").append('worker.onmessage i = ' + index + "<br>");

        if ( index < 25) {
            index++;
            createWorker(index);
        } 
    };
    worker.postMessage(i); // start the worker.
}
</script>
</head>
<body>
<div id="debug"></div>

2)将Web工作者的数量限制为有限数量,并修改我的代码以使用该限制(即,在有限数量的Web工作者之间共享工作负载) - 如下所示:http://www.smartjava.org/content/html5-easily-parallelize-jobs-using-web-workers-and-threadpool < / p>

不幸的是,#1似乎不起作用(在页面加载时只会产生有限数量的Web工作者)。我还应该考虑其他解决方案吗?

4 个答案:

答案 0 :(得分:58)

老问题,让我们复活吧! 准备肾上腺素

由于网络工作人员无法访问主页,因此我一直在寻求使用Web Workers来隔离第三方插件。我会帮助你解决你现在已经解决过的方法,但这适用于互联网。然后,我将从我的研究中提供一些相关信息。

免责声明:在我使用您的代码的示例中,我修改并清理了代码以提供没有jQuery的完整源代码,以便您和其他人可以运行它容易。我还添加了一个计时器,它以ms为单位提醒执行代码的时间。

在所有示例中,我们引用了以下genericWorker.js文件。

genericWorker.js

self.onmessage = function(event) {
    self.postMessage(event.data);
};

方法1(线性执行)

你的第一种方法几乎正常。它仍然失败的原因是,一旦你完成了工作,你就不能删除任何工人。这意味着会发生相同的结果(崩溃),只是更慢。您需要解决的是在创建新工作程序之前添加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>

方法2(线程池)

使用线程池应该可以大大提高运行速度。我们不是使用一些带有复杂术语的库,而是简化它。所有线程池均意味着有一定数量的工作程序同时运行。我们实际上可以从线性示例中修改几行代码以获得多线程示例。下面的代码将找到您拥有的内核数量(如果您的浏览器支持此内容),或者默认为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>

其他方法

方法3(单人,重复任务)

在您的示例中,您反复使用同一个工作人员。我知道你正在简化一个可能更复杂的用例,但有些人在查看时会看到这个并在他们只使用一个工作人员完成所有任务时应用这个方法。

基本上,我们将实例化一个worker,发送数据,等待数据,然后重复发送/等待步骤,直到所有数据都被处理完毕。

在我的计算机上,它的运行速度大约是线程池的两倍。那真让我感到惊讶。我认为线程池的开销会导致速度慢于速度的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>

现在为一些现实世界的shtuff

还记得我说我是如何使用工作人员在我的代码中实现第三方插件的?这些插件具有跟踪状态。我可以启动插件并希望他们不会为应用程序加载太多错误,我可以跟踪主线程中的插件状态并将该状态发送回插件如果插件需要重新加载。我更喜欢第二个。

我已经写了几个有状态,无状态和恢复状态的工人的例子,但我会给你带来痛苦,只做一些简短的解释和一些较短的片段。

首先,一个简单的有状态工作者看起来像这样:

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(状态恢复)

如果我们尽可能地让工人留在记忆中会怎么样,但如果我们确实失去了它,我们可以恢复它的状态吗?我们可以使用某种调度程序来查看用户使用的插件(可能还有一些奇特的算法来猜测用户将来会使用什么)并将这些插件保存在内存中。

关于这一点很酷的部分是我们不再看每个核心的一名工人了。由于工作人员处于活动状态的大部分时间都是闲置的,我们只需要担心它占用的内存。对于大量工人(10到20左右)来说,这根本不会是实质性的。我们可以保持主插件的加载,而不经常使用的插件可以根据需要进行切换。 所有插件仍然需要某种状态恢复。

让我们使用以下工作人员,并假设我们发送&#39;增量&#39;,&#39;减少&#39;或者包含它应该处于的状态的整数

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;
    }
};

这些都是非常简单的例子,但我希望我能帮助理解有效使用多个工人的方法!我很可能正在为这些东西编写调度程序和优化程序,但是谁知道我什么时候能够达到这一点。

祝你好运,编码愉快!

答案 1 :(得分:10)

我的经验是太多工人(> 100)会降低性能。在我的情况下,FF变得非常慢,Chrome甚至崩溃。我将变量与不同数量的工人进行了比较(1,2,4,8,16,32)。工作人员对字符串进行了加密。事实证明,8是最佳工人数量,但这可能会有所不同,具体取决于工人必须解决的问题。

我建立了一个小框架,从工人数量中抽象出来。对工作人员的呼叫被创建为任务。如果允许的最大工作人员数量很多,则新任务将排队并稍后执行。

事实证明,以这种方式回收工人非常重要。您应该在闲置时将它们放在池中,但不要经常调用新的工作者(...)。即使工人被worker.terminate()终止,似乎创建/终止和回收工人之间的性能也存在很大差异。

答案 2 :(得分:2)

你在解决方案#1中链接你的Workers的方式会弹出垃圾收集器来终止Worker实例,因为你仍然在onmessage回调函数的范围内引用它们。

尝试使用此代码:

<script type="text/javascript">
var worker;
$(document).ready(function(){
    createWorker(0);
});
function createWorker(i) {
   worker = new Worker('test.worker.js');
   worker.onmessage = handleMessage;
   worker.postMessage(i); // start the worker.
}
function handleMessage(event) {
       var index = event.data;
       $("#debug").append('worker.onmessage i = ' + index + "<br>");

        if ( index < 25) {
            index++;
            createWorker(index);
        } 
    };
</script>
</head>
<body>
<div id="debug"></div>

答案 3 :(得分:2)

老问题,但是在搜索上出现了,所以...... Firefox中有一个可配置的限制。如果你查看about:config(在FF的地址栏中输入地址),并搜索“工人”,你会看到几个设置,包括这个:

dom.workers.maxPerDomain

默认设置为20。双击该行并更改设置。您需要重新启动浏览器。