Статьи

Простое распараллеливание заданий с использованием веб-работников и пула потоков с HTML5

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

Софи веб-работник

Sinc почти у всех в настоящее время есть несколько ядер, это пустая трата, чтобы не использовать их. В этой статье я покажу, как мы можем использовать простой пул потоков, чтобы еще больше распараллелить это и увеличить время рендеринга на +/- 300%. Вы можете запустить этот пример из следующего местоположения: http://www.smartjava.org/examples/webworkers2/

Код пула потоков

Чтобы протестировать несколько потоков с веб-работниками, я написал простой (и очень наивный) пул потоков / taskqueue. Вы можете настроить максимальное количество одновременных веб-работников при создании этого пула, и любая отправленная вами «задача» будет выполняться с использованием одного из доступных потоков из пула. Обратите внимание, что мы на самом деле не объединяем потоки, мы просто используем этот пул для управления количеством одновременно выполняемых веб-работников.

function Pool(size) {
    var _this = this;
 
    // set some defaults
    this.taskQueue = [];
    this.workerQueue = [];
    this.poolSize = size;
 
    this.addWorkerTask = function(workerTask) {
        if (_this.workerQueue.length > 0) {
            // get the worker from the front of the queue
            var workerThread = _this.workerQueue.shift();
            workerThread.run(workerTask);
        } else {
            // no free workers,
            _this.taskQueue.push(workerTask);
        }
    }
 
    this.init = function() {
        // create 'size' number of worker threads
        for (var i = 0 ; i < size ; i++) {
            _this.workerQueue.push(new WorkerThread(_this));
        }
    }
 
    this.freeWorkerThread = function(workerThread) {
        if (_this.taskQueue.length > 0) {
            // don't put back in queue, but execute next task
            var workerTask = _this.taskQueue.shift();
            workerThread.run(workerTask);
        } else {
            _this.taskQueue.push(workerThread);
        }
    }
}
 
// runner work tasks in the pool
function WorkerThread(parentPool) {
 
    var _this = this;
 
    this.parentPool = parentPool;
    this.workerTask = {};
 
    this.run = function(workerTask) {
        this.workerTask = workerTask;
        // create a new web worker
        if (this.workerTask.script!= null) {
            var worker = new Worker(workerTask.script);
            worker.addEventListener('message', dummyCallback, false);
            worker.postMessage(workerTask.startMessage);
        }
    }
 
    // for now assume we only get a single callback from a worker
    // which also indicates the end of this worker.
    function dummyCallback(event) {
        // pass to original callback
        _this.workerTask.callback(event);
 
        // we should use a seperate thread to add the worker
        _this.parentPool.freeWorkerThread(_this);
    }
 
}
 
// task to run
function WorkerTask(script, callback, msg) {
 
    this.script = script;
    this.callback = callback;
    this.startMessage = msg;
};

Использование пула потоков

Чтобы использовать этот пул потоков, мы просто должны сделать это:

    var pool = new Pool(6);
    pool.init();

Это создаст пул, который позволит одновременно работать максимум 8 потокам. Если мы хотим создать задачу, которая будет выполняться этим пулом, мы просто создадим рабочую задачу и отправим ее следующим образом:

      var workerTask = new WorkerTask('extractMainColor.js',callback,wp);
      pool.addWorkerTask(workerTask);

Это создаст веб-работника из ‘extractMainColor.js’ и зарегистрирует предоставленную функцию как обратный вызов. Когда рабочий будет готов к запуску, последний аргумент будет использоваться для отправки сообщения рабочему. Предостережение об этой реализации. Теперь я предполагаю, что когда веб-работник отправляет сообщение обратно, он закрывается после отправки этого сообщения. Как вы можете видеть в следующем примере:

importScripts('quantize.js' , 'color-thief.js');
 
self.onmessage = function(event) {
    var wp = event.data;
    var foundColor = createPaletteFromCanvas(wp.data,wp.pixelCount, wp.colors);
    wp.result = foundColor;
    self.postMessage(wp);
 
    // close this worker
    self.close();
};

Полученные результаты

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

Хром:

Количество потоков Общее время рендеринга
1 14213
2 9956
3 8778
4 7846
5 6924
6 6309
7 5912
8 5468
9 5201
10 5193
11 5133
12 5208

Результат для Firefox менее впечатляющий, но вы все равно можете увидеть большой выигрыш:

Fire Fox:

Количество потоков Общее время рендеринга
1 17909
2 11273
3 10422
4 10154
5 10115
6 10052
7 10000
8 9997

Как видите, и для Firefox, и для Chrome полезно не просто использовать одного веб-работника, но и разделить задачи. Для Firefox мы можем увидеть большой выигрыш, если будем использовать двух веб-работников, а для Chrome мы продолжаем получать лучшие результаты для 8 или 9 параллельных веб-работников!