Технология HTML5 Web Workers предоставляет веб-приложениям возможности фоновой обработки в виде отдельных потоков, что позволяет JavaScript-приложениям в полной мере использовать преимущества многоядерных процессоров. Кроме того, использование потоков позволяет избежать назойливых предупреждений о долгом выполнении скрипта, которые появляются если скрипт выполняется дольше нескольких секунд.
Введение
До появления стандарта HTML5, все Web-сценарии выполнялись в одном потоке. Подразумевается, что и загрузочные Web-сценарии, выполняющиеся прямо в процессе загрузки Web-страницы, и обработчики событий, и всё остальное выполнялось в одном потоке.
То есть, представьте. Вы жмёте на кнопку, начинаются вычисления, которые вешают ваш поток, и вы ничего не может поделать, кроме как ждать. Как уже говорил, выполнение всех Web-сценариев производится в главном потоке выполнения Web-обозревателя, как и действия пользователя: прокрутка Web-страниц, переходов по гиперссылкам и др. И одновременно выполнять Web-сценарий и обрабатывать действия пользователя Web-обозреватель не может. Поэтому, пока выполняется наш Web-сценарий, Web-обозреватель не сможет обработать щелчок на кнопке или что-то другое.
Конечно, делали костыли, эмулировали многопоточность с помощью setInterval и setTimeout. Но их использование и ряд ограничений накладывал.
Web Workers: полноценная многопоточность
Web Worker — Web-сценарий, работающий в параллельном потоке. Этот поток имеет более низкий приоритет, нежели главный, поэтому в любом случае не сможет нарушить работу; следовательно, задачи обработки действий пользователя и реакции на них всегда будут выполняться незамедлительно. То есть, по сути, фоновые потоки.
Поддержка спецификации браузерами
Браузер | Описание |
Chrome | Поддерживается в версии 3 и выше |
FireFox | Поддерживается в версии 3.5 и выше |
Internet Explorer | Поддерживается в версии 10 и выше |
Chrome | Поддерживается в версии 3 и выше |
Opera | Поддерживается в версии 10.6 и выше |
Safari | Поддерживается в версии 4 и выше |
Доступные действия
Что могут Web Workers и к чему имеют доступ:
- Доступ к объекту
navigator
- Доступ к объекту
location
(read-only) - Доступ к
XMLHttpRequest
- Создавать
setTimeout()
/clearTimeout()
- Создавать
setInterval()
/clearInterval()
- Доступ к кэшу приложения
- Может выполнять Ajax запросы
- Может подгружать другие скрипты с помощью метода
importScripts()
- Может оздавать дочерние потоки
К чему Web Workers доступа не имеют:
-
DOM
(ибо не потоко-безопасно) -
window
document
parent
Достоинства
- Практически любой Web-сценарий можно запустить в качестве Web Worker. Разве что необходимо ещё дописать методы по отправке сообщений.
- Поскольку Web-сценарий выполняется в параллельном потоке, он никак не может нарушить работу самого Web-обозревателя и всех Web-сценариев, выполняющихся в главном потоке. Песочница :3
Недостатки
- Web Worker не имеет доступа к window.document. Это означает, что у потоков отсутствует возможность непосредственного доступа к веб-странице и DOM.
- Web Worker не может обращаться к переменным, объявленным в общем потоке. Ну, это очевидно.
То есть, по сути, недостатков то и нет. Если результат выполнения потока как-то надо отобразить на странице, то по получении сообщения от потока, можно в главном потоке соответствующие изменения внести. Что касается глобальных переменных…
Глобальные переменные — зло
В своё время много намучился с потоками в Win API под C++. К счастью, W3C думает о программистах, поэтому работать с потоками в HTML5 одно удовольствие.
Применение
Кто с Win API работал, знает о дилемме использования потоков. Порой, их использование только увеличивает по времени выполнение программы. Всё дело во времени на создание потока и переключение контекста.
Так что потоки больше используются для интерактивности, нежели для ускорения. Конечно, если вам необходимо выполнить немало фоновых вычислений, то использование Web Workers очень удачное решение.
В качестве другого примера использования Web Workers можно привести приложение, которое получает сообщения с сервера новостей и размещает их на основной веб-странице по мере их получения с сервера. Для связи с сервером могут использоваться веб-сокеты или серверные события.
Программный интерфейс HTML5 Web Workers
Переходим к самом главному — использованию потоков. Принцип работы в целом очень прост. Вы создаёте объект Worker и передаёте ему JavaScript файл, который необходимо выполнить. Настраиваем обработчики сообщений и ошибок, посылаемых потоков. Для передачи данных используем метод postMessage()
.
Проверка поддержки в браузере
Прежде, чем использовать Web Workers, следует убедиться, что браузер предоставляет необходимую поддержку. HTML5 уже довольно давно вышел, так что, большинство юзеров сидят под браузерами с поддержкой этой технологии. Но мало ли, может понадобиться проверять.
function testWW(){ if(typeof(Worker) !== "undefined") { //alert('WW поддерживается'); } } //или function testWW2(){ if (!!window.Worker) { //alert ('Работает!'); } }
Загрузка и выполнение дополнительного JS-кода
Приложение, в состав кторого входит несколько JavaScript-файлов, может содержать элементы <script>
, осуществляюшие синхронную загрузку JS-файлов по мере загрузки страницы. Но поскольку потоки Web Workers не имеют доступа к объекту document, можно использовать другой механизм, обеспечивающий синхронное импортированние JavaScript-файлов изнутри потоков:
importScripts("help.js");
Импортируемый файл просто грузится в существующий поток и выполняется в нём. Одним вызовом метода можно импортировать сразу несколько сценариев. В этом случае сценарии выполняются в той очерёдности, в которой они указаны:
importScripts("help1.js", "help2.js");
Создание потоков Web Workers
Потоки Web Workers инициализируются URL-адресом JavaScript-файла, в котором код потока, подлежащий выполнению. Этот код устанавливает обработчик событий и обменивается сообщениями с породившим его сценарием. Для запуска:
worker = new Worker ("путь к скрипту");
В качестве единственного параметра конструктору передаётся строка с интернет-адресом файла Web-сценария, хранящего код Web Worker.
Обмен сообщениями с потоками
После того как поток создан, с ним можно обмениваться сообщениями, используя для этого метод postMessage()
. С помощью этого метода можно пересылать большинство JS-объектов.
Прекращение выполнения потоков
Потоки Web Workers не могут самостоятельно прекратить работу: остановить их может лишь страница, которая их запустила. По аналогии с WIN API. Там необходимо было дескрипторы потоков закрывать и т.д. и т.п. Если в потоке больше нет необходимости, следует освободить занимаемые ресурсы. Кроме того, в некоторых случаях, если поток выполняется слишком долго, юзер может захотеть преждевременно прекратить его выполнение. Для прекращения работы потока, следует вызвать метод terminate()
. Поток, выполнение которого прекращено, перестаёт отвечать на сообщения и выполнять любые другие вычисления. Перезапустить поток нельзя, можно лишь создать новый.
Пример реализации простейшего Web Worker
Как пример разработаем приложение, где по нажатию кнопки запускается поток, который что-то там вычисляет, а потом возвращает результат вычислений. Мне сейчас лень голову ломать над примером алгоритма. Пускай внутри потока по таймауту отправляются результаты вычислений. Ну, или скажем, по таймату происходит запрос к серваку на получение последних новостей, а затем происходит возврат новости в основной поток, где она и публикуется на странице.
То есть, у нас есть скрипт, выполняющийся в потоке. Пускай webworker.js.
// Получаем сообщение из основного потока onmessage = function(e) { setInterval(messageByTimeout, 3000,e.data); }; function messageByTimeout(e){ // Отправляем сообщение в основной поток postMessage('Прокнул поток №'+e); }
На странице у нас 2 кнопки. Одна запускает рабочий поток новый, вторая завершает все текущие потоки. Сразу весь код приведу.
//массив (пул) потоков workers = new Array(); //вызывается при нажатии на кнопку запуска потока function startCalc(){ //запуск потока workers[workers.length] = new Worker('webworker.js'); //подписка на обработчик, перехватывающий сообщения об ошибках workers[workers.length-1].addEventListener("error", errorHandler, true); //подписка на обработчик, перехватывающий сообщения, направляемые потоком workers[workers.length-1].addEventListener("message", messageHandler, true); //отправка сообщения потоку c аргументами (в данном случае ID потока) workers[workers.length-1].postMessage(workers.length-1); } //вызывается при нажатии на кнопку остановки function stopCalc(){ for(i=0;i<workers.length;++i) workers[i].terminate(); workers = new Array(); } //обрабочик сообщений от потока function messageHandler(e){ //в консоль console.log(e.data,e); //или на страницу document.getElementById('result').innerHTML +=e.data+'</br>'; } //обработчик ошибок function errorHandler(e){ console.log(e.message,e); }
Если хотите наглядно увидеть, как это работает, понаимайте на кнопочки снизу.
Если два раза нажать на кнопку запуска, то создадутся два потока. После двух сообщений от каждого потока жмём кнопку остановки, результат будет таким.