Параллельные вычисления на веб-странице

Просто обычная практика выполнения клиентских сценариев в единственном потоке хороша ровно до того момента, пока не надо произвести действительно серьезных и тяжелых вычислений.

Разумеется, подобных ситуаций не создавалось в те времена, когда призванием JavaScript-сценария было подсветить активную кнопку или красиво отобразить всплывающую подсказку. Но со временем задачи перед клиентскими сценариями встают все серьезней и серьезней, а блокировать процесс работы с веб-страницей, пока выполняется, например, полутораминутная обработка изображения перед загрузкой, совсем не хочется. И наблюдать js-сообщения вроде показанного на рис. 100 — согласитесь, тоже не самое приятное занятие. И вот тут на помощь приходят наши веб-работники, проводящие подобные «тяжелые» операции в режиме фоновых вычислений.

В данном случае под фоновыми вычислениями мы подразумеваем код, выполняющийся вне основного потока выполнения кода вебстраницы, еще конкретнее — вне основного потока браузера. Для

Рис. 100. Знакомо?

Реализации этой модели сначала создадим экземпляр фоновой обработки (Worker):

Var worker; if (!window. Worker){

Alert(‘WebWorkers не подерживаются!); } else {

Worker = new Worker(‘webWorker. js’);

}

Теперь создадим файл webWorker. js со следующим содержанием:

Onmessage = function(e){ Message("Hi!");

}

Что тут происходит, станет ясно чуть позже.

Если все в порядке и ваш браузер достаточно современен, то «работник» уже создан и готов к работе. Теперь ему можно давать задания. Как? С помощью уже знакомого нам механизма веб-сообщений Web Messaging API:

Worker. essage(‘Hello!’);

Таким образом можно посылать сообщения фоновому процессу. Соответственно, сообщения от worker сможет читать функция обратного вызова, ожидающая связи:

Worker. onmessage = function(event){

Alert("Получено сообщение: "+event. data);

Испытаем механизм в действии (рис. 101).

Рис. 101. Привет от Worker

Посылать фоновому процессу и принимать от него можно только текстовые сообщения, чего в принципе достаточно — формат JSON поможет нам оперировать более высокими материями.

Как мы видим из кода webWorker. js, он оперирует точно таким же API. Можно сделать его немножко «умнее»:

Onmessage = function(e){ if(e. data == "Hello!"{ essage("Hi!");

} else {

Age("Well… And where ‘Hello!’?");

}

Отлично, коду worker доступен Wqeb Mesage API! А что еще? Наверное, и все остальные возможности полноценного JavaScript-сценария?

К сожалению, придется немого разочаровать — доступно далеко не все. Что делать, с правами у рабочего класса все еще не очень хорошо. Прежде всего ограничения касаются доступа к Document Object Model (DOM). Это, в частности, обозначает, что доступа к коллекциям вроде document.* или window.* нет, и это серьезное ограничение. Единственное исключение — объект navigator. Что же нам остается?

В число прав рабочих, кроме вышеупомянутых onmessage и age, входит возможность прекращать работу — метод close(), использовать таймеры (setTimeout, setInterval), использовать объект XMLHTTPRequest, работать с объектом location (определяя свое местоположение).

Также worker доступны все нативные функции JavaScript (включая eval()) и новые возможности веба — WebSockets и Web-базы данных.

Как видите, возможностей хватает, но приходится оговориться — все это перечислено в спецификации. В жизни пока реализовано далеко не все. Впрочем, не будем о грустном.

Еще две возможности, реализованные в WebWorkers, обеспечивают базовые возможности масштабирования. Во-первых, это возможность загружать сторонние сценарии и библиотеки с помощью функции import-Scripts, во-вторых, самим использовать фоновые вычисления.

Теперь попробуем сотворить с помощью worker что-нибудь условно полезное. Например, заставим их вычислять в фоновом режиме жизненно необходимые в быту числа Фибоначчи. Для этого сделаем небольшую разметку:

<html>

<head>

<meta charset="cp1251">

<title>WebWorkers API</title>

<script src="../jquery-1.4.4.min. js"></script>

<script> var worker;

Worker = new Worker(‘webWorker. js’); function getNumber(){ worker. age(‘get’);

}

Worker. onmessage = function(event){ alert(event. data);

}

</script>

</head>

<body>

<img src = "Fibonacci2.jpg">

<br>

<input type = "button" onclick = "getNumber();" value = "Получить число"> </body>

</html>

Код ‘webWorker. js’ теперь будет таким:

Var current = 1; var prev = 0: function getNumbers(){ next = current+prev: age(next): prev = current; current=next:

}

Onmessage = function(e){ if(e. data == "get"){ getNumbers():

}

}

Тут мы начали сразу с третьего числа, но я думаю, что Леонардо Пизански нас простит. Гораздо важнее показать, что мы перенесли часть нашего JavaScript-сценария в фон. Щелкаем по кнопке и наблюдаем за возрастающими цифрами — все работает! (рис. 102).

Рис. 102. Фибоначчи одобряет

Впрочем, настоящей работы в фоне мы тут не видим. Давайте исправим это положение. Пусть посетитель страницы будет занят каким-нибудь делом — например, пишет статью о средневековом математике, а цифры появляются сами по себе (для вдохновения пишущего).

Сначала сверстаем интерфейс:

<div style = "text-align: left;widht=300px;">

<img src = "Fibonacci2.jpg"><textarea ></textarea>

<br>

<div id="info"></div>

<input type = "button" onclick = "startGetNumber();" value = "Начать фибонач-чить!">

<input type = "button" onclick = "resetNumber();" value = "Reset">

<input type = "button" onclick = "stop();" value = "Stop">

<br>

<input type = "button" onclick = "terminate()" value = "Kill Worker!">

</div>

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

Worker = new Worker(‘webWorker. js’);

Function startGetNumber(){ worker. ge(‘ start’);

}

Function resetNumber(){ worker. ge(‘ reset’);

}

Function stop(){ worker. ge(‘ stop’);

}

Worker. onmessage = function(event){ $( ‘#info’).html(event. data);

}

То есть мы просто посылаем команды нашему работнику, чтобы тот вкалывал. Сам файл webWorker. js’ примет следующий вид:

Var stop = 0; var current = 1; var prev = 0;

Function getNumbers(){

Next = current+prev;; prev = current; current=next; ge(next); if(stop!= 1){

SetTimeout("getNumbers()", 1000);

}

}

Onmessage = function(e){ if(e. data == "start"){ stop = 0; getNumbers();

}

If(e. data == "reset"){ current = 1; prev = 0;

}

If(e. data == "stop"){ stop = 1;

}

}

Тут тоже все просто — функция обратного вызова оперирует глобальными переменными, управляя работой функции getNumbers. Для полноты добавим еще одну функцию-обработчик:

Function terminate(){ worker. terminate():

}

Метод terminate в соответствии со своим названием попросту уничтожает объект Worker. Результат — на рис. 103.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *