【问题标题】:Number of Web Workers LimitWeb Worker 数量限制
【发布时间】:2012-11-26 22:21:19
【问题描述】:

问题

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

示例

主要的 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 Worker 的数量限制为有限数量,并修改我的代码以使用该限制(即,在有限数量的 Web Worker 之间分担工作负载) - 如下所示:http://www.smartjava.org/content/html5-easily-parallelize-jobs-using-web-workers-and-threadpool

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

【问题讨论】:

  • 您在其他浏览器中是否也遇到此限制?如果没有,请用 FF 标记您的问题。
  • 每个浏览器都有网络工作者限制(Firefox 有 20,Chrome 60+,Opera 16);但是,您可以在 Firefox -> dom.workers.maxPerDomain 中更改它;至于你的实际问题,如果你能或不能避免这个限制,我不确定。 “工人(因为这些背景脚本在本文中被称为)相对重量级,并且不打算大量使用。”你能给出一个确切的情况,你希望使用 20 多个工人吗?
  • 看看here
  • @MariusBalaban 你的评论应该是一个答案。
  • @MariusBalaban “不可能直截了当”这个问题的正确答案 :) 至少目前是这样。

标签: javascript


【解决方案1】:

老问题,让我们重温一下! 准备肾上腺素

我一直在研究使用 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;
    }
};

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

祝你好运,编码愉快!

【讨论】:

  • 在一个 Stackoverflow 答案中找到您过去、现在和未来问题的答案的少数几次之一!
  • 我希望有办法为这个答案颁发卓越奖
【解决方案2】:

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

我建立了一个小框架来从工人数量中抽象出来。对工作人员的调用被创建为任务。如果允许的最大工作线程数处于忙碌状态,则会将新任务排队并稍后执行。

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

【讨论】:

  • 先生,您能告诉我您的 CPU 有多少个内核,您在测试时是否超线程。
  • 我在 2 核 MacBook Air 上测试过。
【解决方案3】:

老问题,但在搜索时出现,所以... Firefox 中有一个可配置的限制。如果你查看about:config(作为地址放在FF的地址栏中),然后搜索'worker',你会看到几个设置,包括这个:

dom.workers.maxPerDomain

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

【讨论】:

  • 默认似乎是 512 现在(2020,Firefox 82+)
【解决方案4】:

您在解决方案 #1 中链接您的 Worker 的方式会要求垃圾收集器终止 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>

【讨论】:

    猜你喜欢
    • 2012-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-06
    • 1970-01-01
    • 2021-12-14
    • 1970-01-01
    • 2012-06-28
    相关资源
    最近更新 更多