Подтвердить что ты не робот

Количество ограничений для веб-работников

ПРОБЛЕМА

Я обнаружил, что существует ограничение на количество веб-работников, которое может быть создано браузером.

Пример

основной 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 генерируется только 20 строк вывода (версия 14.0.1, Windows 7).

Вопрос

Есть ли способ обойти это? Единственные две идеи, о которых я могу думать, следующие:

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) Ограничьте число веб-работников конечным числом и измените мой код, чтобы работать с этим лимитом (т.е. делиться рабочей нагрузкой через конечное число веб-работников) - примерно так: http://www.smartjava.org/content/html5-easily-parallelize-jobs-using-web-workers-and-threadpool

К сожалению, # 1, похоже, не работает (только конечное число веб-работников будет порождено при загрузке страницы). Есть ли другие решения, которые я должен рассмотреть?

4b9b3361

Ответ 1

Старый вопрос, пусть оживит его! готовит эпинефрин

Я искал использование Web Workers для изоляции сторонних плагинов, так как веб-рабочие не могут получить доступ к главной странице. Я помогу вам с помощью ваших методов, которые, я уверен, вы уже решили, но это для интернета. Затем я дам некоторую релевантную информацию из моих исследований.

Отказ от ответственности. В примерах, которые я использовал в вашем коде, я изменил и очистил код, чтобы предоставить полный исходный код без jQuery, чтобы вы и другие могли легко запускать его. Я также добавил таймер, который предупреждает время в мс для выполнения кода.

Во всех примерах мы ссылаемся на следующий 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. Я обнаружил, что этот код работает примерно на 6 раз быстрее, чем оригинал на моей машине с 8 ядрами.

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 (одиночный рабочий, повторная задача)

В вашем примере вы используете одного и того же работника снова и снова. Я знаю, что вы упрощаете, вероятно, более сложный прецедент, но некоторые люди просматривают это и применяют этот метод, когда они могут использовать только одного работника для всех задач.

По существу, мы будем создавать экземпляр рабочего, отправлять данные, ждать данных, а затем повторять шаги отправки/ожидания до тех пор, пока все данные не будут обработаны.

На моем компьютере это работает примерно в два раза быстрее, чем поток пула потоков. Это меня действительно удивило. Я думал, что накладные расходы из пула потоков приведут к тому, что он будет медленнее, чем только на 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 или около того) это не будет существенным. Мы можем поддерживать загрузку первичных плагинов, а те, которые не используются, часто меняются по мере необходимости. Все плагины все равно нуждаются в каком-то восстановлении состояния.

Давайте использовать следующий рабочий и предположим, что мы либо отправляем "increment", "декремент", либо целое число, содержащее состояние, в котором оно должно находиться.

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

Все это довольно простые примеры, но я надеюсь, что я помог понять методы эффективного использования нескольких сотрудников! Я, скорее всего, буду писать планировщик и оптимизатор для этого материала, но кто знает, когда я доберусь до этого момента.

Удачи и счастливого кодирования!

Ответ 2

Мой опыт в том, что слишком много рабочих ( > 100) снижает производительность. В моем случае FF стал очень медленным, и Chrome даже разбился. Я сравнивал варианты с разным количеством работников (1, 2, 4, 8, 16, 32). Рабочий выполнил шифрование строки. Оказалось, что 8 - оптимальное количество рабочих, но это может различаться в зависимости от проблемы, которую должен решить рабочий.

Я создал небольшую основу для абстрагирования от количества работников. Звонки на рабочих создаются как задачи. Если максимальное количество занятых занято, новая задача ставится в очередь и выполняется позже.

Оказалось, что очень важно перерабатывать рабочих в таком подходе. Вы должны держать их в пуле, когда они простаивают, но не назовите нового Рабочего (...) слишком часто. Даже если рабочие прекращаются рабочим .terminate(), кажется, что существует большая разница в производительности между созданием/прекращением и рециркуляцией работников.

Ответ 3

Как вы цепляете своих Рабочих в решении №1, импичруйте сборщик мусора, чтобы прервать экземпляры 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>

Ответ 4

Старый вопрос, но появляется при поиске, поэтому... В Firefox есть настраиваемый предел. Если вы посмотрите в about:config (укажите адрес в адресной строке FF) и выполните поиск "worker", вы увидите несколько настроек, в том числе:

dom.workers.maxPerDomain

Установите значение 20 по умолчанию. Дважды щелкните по строке и измените настройку. Вам нужно будет перезапустить браузер.