Знакомство
Web Workers — технология, позволяющая выполнять Javascript в фоновом режиме, что в свою очередь позволяет воспользоваться преимуществами многоядерных архитектур.
Поддержка браузерами Web Workers
Где использовать
- самое очевидное — для сложных, объемных вычислений;
- обработка/анализ изображений и видео;
- разбор объемных данных полученных после вызова XMLHTTPRequest;
- анализ вводимого текста пользователем "на лету", не блокируя интерфейс;
- запросы к локальным базам данных.
Вообще некоторые утверждают, что применение Web Workers практически ограничивается фантазией разработчика несмотря на ограничения технологии. Нужно помнить, что вызов фонового обработчика, передача ему и получение от него данных занимает какое-то время. Поэтому использование Web Workers должно быть обдуманным.
Возможности и ограничения
Web Workers не имеет доступа к:
- DOM;
- объектам window, document, parent.
При этом доступно:
- XMLHttpRequest;
- объекты navigator, location;
- setTimeout()/clearTimeout() и setInterval()/clearInterval();
- внедрение внешних скриптов с помощью метода importScripts();
- порождение субворкеров;
- Application Cache.
Пример
Целью этого примера является базовое знакомство с Web Workers, чтобы можно было начать использовать не тратить очень много времени на изучение спецификации.
Для примера будем рисовать на канве круг, вычисление позиций которого посчитаем тяжелой операцией. Вычисления выносим в отдельный js файл, чтобы появилась возможность использовать фоновые вычисления. Основной код:
window.onload = function() { var canvas = document.getElementById("scene"), // канва ctx = canvas.getContext("2d"), canvasW = canvas.offsetWidth, canvasH = canvas.offsetHeight, posY, posX = 0, // начальная позиция angle = 0, // начальный угол r = 30; /* цвета и стили фигуры */ ctx.lineWidth = 2; ctx.fillStyle = "green"; ctx.strokeStyle = "black"; /* если поддерживается web workers используем параллельные вычисления */ if(!!window.Worker) { /* создаем worker в качестве параметра указываем js файл и путь к нему, где будут проводиться тяжелые операции */ var worker = new Worker("worker.js"); /* делаем запрос чтобы получить первые результаты передаем необходимые данные для вычислений данные можно передавать переменными/константами. Если данных несколько - формат JSON */ worker.postMessage({ posX: posX, angle: angle, r: r, canvasW: canvasW, canvasH: canvasH }); /* render для поддерживающих web worker */ function updateWorker() { /* получаем результаты обработчик сработает как только вычисления завершаться */ worker.onmessage = function (event) { var data = event.data; posX = data.posX, posY = data.posY, angle = data.angle; }; ctx.clearRect(0, 0, 600, 400); /* рисуем */ ctx.beginPath(); ctx.arc(posX, posY, r, 0, Math.PI*2, false); ctx.closePath(); ctx.stroke(); ctx.fill(); /* отправляем новый запрос */ worker.postMessage({ posX: posX, angle: angle, r: r, canvasW: canvasW, canvasH: canvasH }); requestAnimFrame(updateWorker); }; requestAnimFrame(updateWorker); // запускаем рендер } /* если web workers не поддерживается - по старинке: один поток */ else { /* render для не поддерживающих web workers */ function update() { ctx.clearRect(0, 0, 600, 400); /* рисуем */ ctx.beginPath(); ctx.arc(posX, posY, r, 0, Math.PI*2, false); ctx.closePath(); ctx.stroke(); ctx.fill(); // вычисления posY = canvasH/2 + Math.sin(angle) * 50; angle += 0.05; posX+=0.5; if(posX>canvasW+r) posX=-r; requestAnimFrame(update); } requestAnimFrame(update); } }
Код воркера (worker.js):
/* начинаем вычиления как только пришли исходные данные */ onmessage = function(event) { var data = event.data, canvasH = data.canvasH, angle = data.angle, posX = data.posX, canvasW = data.canvasW, r = data.r; // вычисления posY = canvasH/2 + Math.sin(angle) * 50; angle += 0.05; posX+=0.5; if(posX>canvasW+r) posX=-r; // отправляем назад результаты postMessage({ posX: posX, posY: posY, angle: angle }); }
В живую. Данный вариант далек от оптимального, т.к. отправка/получение данных выходят ощутимо тяжелее, чем расчеты. Приблизим данный пример к реальности: будем просчитывать 100 следующих точек для шара. Алгоритм:
- создаем воркер, отправляем начальные данные, получаем массив координат для первых ста позиций;
- запускаем рендер (начинается отрисовка по уже имеющимся данным);
- пока она идет, в фоне запускаем вычисления следующих ста точек;
- когда достигли конца массива с полученными раннее данными, обновляем их свежими, продолжаем рисовать;
- в фоне запускаем вычисления следующей партии координат.
Основной код:
window.onload = function() { var canvas = document.getElementById("scene"), // канва ctx = canvas.getContext("2d"), canvasW = canvas.offsetWidth, canvasH = canvas.offsetHeight, posY, posX = 0, angle = 0, r = 30, counter = 0, arrPosX = [], arrPosY = [], startAnim = true; /* цвета и стили фигуры */ ctx.lineWidth = 2; ctx.fillStyle = "green"; ctx.strokeStyle = "black"; /* если поддерживается web workers используем параллельные вычисления */ if(!!window.Worker) { var worker = new Worker("worker-2.js"); /* делаем запрос чтобы получить первые результаты */ worker.postMessage({ posX: posX, angle: angle, r: r, canvasW: canvasW, canvasH: canvasH }); /* обработчик получения результат вычислений */ worker.onmessage = function (event) { /* данные сохряняем во временные переменные чтобы их использовать когда нужно, а не когда завершатся вычисления */ var data = event.data, arrPosXTemp = data.posX, arrPosYTemp = data.posY, angleTemp = data.angle; // если это первый вызов обработчика, тогда запускаем рендер if(startAnim==true) { requestAnimFrame(updateWorker); startAnim = false; arrPosX = arrPosXTemp; // первые полученные данные перегоняем в массивы, с которыми работает рендер arrPosY = arrPosYTemp; angle = angleTemp; } }; /* рендер */ function updateWorker() { ctx.clearRect(0, 0, 600, 400); /* рисуем по вычисленным заранее координатам */ ctx.beginPath(); ctx.arc(arrPosX[counter], arrPosY[counter], r, 0, Math.PI*2, false); ctx.closePath(); ctx.stroke(); ctx.fill(); /* если счетчик = 0, запускаем в фоне вычисления новой партии координат */ if(counter===0) { worker.postMessage({ posX: arrPosX[99], angle: angle, r: r, canvasW: canvasW, canvasH: canvasH }); counter++; } /* если достигли конца просчитанных данных, запрашиваем новую просчитанную партию из временных переменных */ else if(counter===99) { arrPosX = arrPosXTemp; arrPosY = arrPosYTemp; angle = angleTemp; counter = 0; } else { counter++; } requestAnimFrame(updateWorker); }; } [...]
Код воркера:
onmessage = function(event) { var data = event.data; canvasH = data.canvasH, angle = data.angle, posX = data.posX, canvasW = data.canvasW, r = data.r, arrPosX = [], arrPosY = []; /* вычиляем координаты 100 следующих точек */ arrPosX[0] = posX; for(i=0; i<100; i++) { arrPosY[i] = canvasH/2 + Math.sin(angle) * 50; angle += 0.05; arrPosX[i+1]= arrPosX[i]+0.5; if(arrPosX[i+1]>canvasW+r) arrPosX[i+1]=-r; } // отправляем назад результаты postMessage({ posX: arrPosX, posY: arrPosY, angle: angle }); }
Заметка
Воркеров можно запускать несколько.
Материалы
- MDN :: Using web workers
- Introduction to HTML5 Web Workers: Use Cases and Identify Hot Spots
- Web Workers за работой