Web Worker限制数量

Bil*_*ill 35 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>
Run Code Online (Sandbox Code Playgroud)

test.worker.js

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

    self.postMessage(i);
};
Run Code Online (Sandbox Code Playgroud)

使用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>
Run Code Online (Sandbox Code Playgroud)

2)将Web工作者的数量限制为有限数量,并修改我的代码以使用该限制(即,在有限数量的Web工作者之间共享工作负载) - 如下所示:http://www.smartjava.org /内容/ HTML5的易于并行化的作业,使用的web -工人-和线程池

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

Eva*_*edy 62

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

我一直在寻找使用Web Workers来隔离第三方插件,因为Web worker无法访问主机页面.我会用你的方法帮助你,我相信你现在已经解决了,但这是为了互联网.然后我会从我的研究中提供一些相关信息.

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

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

genericWorker.js

self.onmessage = function(event) {
    self.postMessage(event.data);
};
Run Code Online (Sandbox Code Playgroud)

方法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>
Run Code Online (Sandbox Code Playgroud)

方法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>
Run Code Online (Sandbox Code Playgroud)

其他方法

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

在您的示例中,您一遍又一遍地使用同一个工作程序.我知道你正在简化一个可能更复杂的用例,但是一些人在查看时会看到这个并且当他们只使用一个worker来完成所有任务时应用这个方法.

本质上,我们将实例化一个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>
Run Code Online (Sandbox Code Playgroud)

方法4(重复的工作者w /线程池)

现在,如果我们将前一个方法与线程池方法结合起来怎么办?从理论上讲,它应该比以前更快.有趣的是,它的速度与我之前的机器上的速度几乎相同.

也许这是每次调用时发送worker引用的额外开销.也许这是额外的工人在执行期间被终止(在我们得到时间之前只有一个工人不会被终止).谁知道.找到这个是另一个时间的工作.

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>
Run Code Online (Sandbox Code Playgroud)

现在为一些现实世界的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;
    }
};
Run Code Online (Sandbox Code Playgroud)

它根据收到的消息执行一些操作并在内部保存数据.这很棒.它允许mah插件开发者完全控制他们的插件.主应用程序实例化他们的插件一次,然后将发送消息给他们做一些动作.

当我们想要一次加载多个插件时,会出现问题.我们做不到,所以我们能做什么?

让我们考虑几个解决方案.

解决方案1(无状态)

让我们使这些插件无状态.基本上,每次我们想让插件执行某些操作时,我们的应用程序应该实例化插件,然后根据其旧状态发送数据.

发送的数据

{
    action: 'increment',
    value: 7
}
Run Code Online (Sandbox Code Playgroud)

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
    });
};
Run Code Online (Sandbox Code Playgroud)

这可能有用,但如果我们处理大量数据,这将开始看起来像一个不太完美的解决方案.另一个类似的解决方案可能是为每个插件安装几个较小的工作人员,并且每个插件只发送少量数据,但我对此也感到不安.

解决方案2(状态恢复)

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

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

让我们使用下面的worker并假设我们发送'increment','decrement'或者包含它应该处于的状态的整数.

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;
    }
};
Run Code Online (Sandbox Code Playgroud)

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

祝你好运,编码愉快!

  • 这是为数不多的能够在一个 Stackoverflow 答案中找到您过去、现在和未来问题答案的机会之一! (7认同)

Bjö*_*ner 11

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

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

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

  • 我在具有 2 个内核的 MacBook Air 上对其进行了测试。 (2认同)

小智 5

老问题,但在搜索时出现,所以...... Firefox 中有一个可配置的限制。如果您查看about:config(在 FF 的地址栏中输入地址)并搜索“worker”,您将看到几个设置,包括以下一项:

dom.workers.maxPerDomain
Run Code Online (Sandbox Code Playgroud)

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

  • 现在默认值似乎是“512”(2020,Firefox 82+) (5认同)

nfr*_*ure 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>
Run Code Online (Sandbox Code Playgroud)