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