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