javascript Web Workers数量限制

i1icjdpr  于 2023-11-15  发布在  Java
关注(0)|答案(4)|浏览(240)

问题

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

示例

main 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)对Web worker进行菊花链连接,即,使每个Web worker生成下一个Web worker
范例:

<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似乎不起作用(只有有限数量的web worker会在页面加载时产生)。我应该考虑其他解决方案吗?

lnxxn5zx

lnxxn5zx1#

我一直在研究使用Web Workers来隔离第三方插件,因为Web Workers不能访问主机页面。我会帮助你解决你的方法,我相信你现在已经解决了,但这是为互联网。然后我会给予一些相关的信息,从我的研究。
第一个月
在所有示例中,我们引用以下genericWorker.js文件。“

  • 通用工作器.js*
self.onmessage = function(event) {
    self.postMessage(event.data);
};

字符串

方法1(线性执行)

你的第一个方法几乎可以工作了。它仍然失败的原因是,你没有删除任何工人,一旦你完成了他们。这意味着同样的结果会发生什么事只不过速度较慢。您所需要修正的就是在建立新的背景工作之前加入worker.terminate();,以便从内存中移除旧的背景工作。请注意,这会导致应用程序执行太多速度较慢,因为必须创建、运行和销毁每个工作进程,然后才能运行下一个工作进程。

  • 线性.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倍。

  • 线程池.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(单个工人,重复任务)

在您的示例中,您反复使用同一个工作线程。我知道您在简化一个可能更复杂的用例,但某些查看的用户会看到这一点,并在他们可以只使用一个工作线程来完成所有任务时应用此方法。
实际上,我们将示例化一个工作线程,发送数据,等待数据,然后重复发送/等待步骤,直到所有数据都处理完毕。
在我的电脑上,它的运行速度大约是线程池的两倍。这实际上让我很惊讶。我以为线程池的开销会导致它的速度慢于1/2。

  • 重复工作者.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(重复工作线程,带线程池)

现在,如果我们把前面的方法和线程池方法结合起来会怎么样呢?理论上,它应该比前面的方法运行得更快。有趣的是,在我的机器上,它的运行速度和前面的方法差不多。
也许是因为每次调用时都要发送工作线程的引用,这会带来额外的开销,也许是因为在执行过程中会终止额外的工作线程(在我们得到时间之前,只有一个工作线程不会被终止),谁知道呢?要找到这个问题,就得另找时间了。

  • 重复执行绪集区.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>

现在来点真实的事#

还记得我说过我是如何使用工作线程在我的代码中实现第三方插件的吗?这些插件有一个状态需要跟踪。我可以启动插件,希望它们不会加载太多的应用程序崩溃,* 或者 * 我可以在我的主线程中跟踪插件状态,如果插件需要重新加载,则将该状态发送回插件。我更喜欢第二个。
我已经写了几个关于有状态、无状态和状态恢复工作者的例子,但我会省去你的麻烦,只做一些简短的解释和一些简短的片段。
首先,一个简单的有状态工作者如下所示:

  • 状态工作者.js*
var i = 0;

self.onmessage = function(e) {
    switch(e.data) {
        case 'increment':
            self.postMessage(++i);
            break;
        case 'decrement':
            self.postMessage(--i);
            break;
    }
};


它会根据收到的消息执行一些操作,并在内部保存数据。这很棒。它允许mah插件开发者完全控制他们的插件。主应用程序示例化他们的插件一次,然后会发送消息让他们执行一些操作。
当我们想同时加载几个插件时,问题就来了。我们不能这样做,所以what can we do?
让我们想想几个解决方案。

解决方案1(无状态)

让我们把这些插件变成无状态的。本质上,每次我们想让插件做些什么的时候,我们的应用程序都应该示例化插件,然后根据它的旧状态向它发送数据。
data sent

{
    action: 'increment',
    value: 7
}

  • 无状态工作者.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保存在内存中,但如果它丢失了,我们可以恢复它的状态,我们可以使用某种调度程序来查看用户一直在使用的插件(也许还可以使用一些奇特的算法来猜测用户将来会使用什么),并将其保存在内存中。

最酷的是,我们不再考虑每个内核一个worker。由于大多数时候worker都是活动的,所以我们只需要担心它占用的内存。对于大量的worker,(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;
    }
};


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

5kgi1eie

5kgi1eie2#

我的经验是,太多的工人(> 100)降低性能。在我的情况下,FF变得非常缓慢,Chrome甚至崩溃。我比较了不同数量的工人的变体(1,2,4,8,16,32). worker对一个字符串进行加密。结果表明,8是最佳的worker数量,但这可能会有所不同,这取决于工人必须解决的问题。
我建立了一个小框架来抽象工作者的数量。对工作者的调用被创建为任务。如果工作者的最大允许数量是忙碌,则新任务将排队并稍后执行。
事实证明,以这种方式回收工作者是非常重要的。当它们空闲时,你应该将它们保存在一个池中,但不要太频繁地调用new Worker(...)。即使工作者被worker.terminate()终止,似乎创建/终止和回收工作者之间的性能差异很大。

bnl4lu3b

bnl4lu3b3#

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

dom.workers.maxPerDomain

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

juud5qan

juud5qan4#

在解决方案#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>

字符串

相关问题