Многопоточность в JavaScript

Да, в JavaScript тоже есть многопоточность(либо подобие) и реализована по средством инструмента Web Workers, о использовании которого и поговорим в данном посте.

Идея реализации: “тяжелые” операции, которые можно выполнять асинхронно(не блокирую работу браузера), выносятся в отдельные модули-файлы; это может быть: обработка большого количества данных, кеширование, проверка правописания, фильтрация изображений на canvas. В браузере для каждого такого файла создается объект Worker, по средством которого и осуществляется коммуникация.

Другими словами: используя технологию Web Workers можно избежать появления вот этого сообщения

Проверка поддержки браузером

Для проверки поддерживает ли браузер технологию Web Workers достаточно проверить наличие объекта window.Worker:

if (!!window.Worker)
{
    //поддерживается
}

 Простой пример

На сайте w3 дали довольно наглядный пример, который я немного модифицировал расширив передачу данных в обе стороны. Итак, у нас есть 2 файла: основной(для простоты пишем внутри хтмл файла) test.html и файл воркера worker.js. Мы задались целью выводить на экран простые числа в заданном диапозоне, но, так как операция по определению является ли число простым довольно ресурсоемкая, особенно для больших чисел, мы вынесем ее в отдельный фоновый поток (worker). Код этой функции и будет находиться в файле worker.js.

Итого, test.html:

<!DOCTYPE HTML>
<html>
<body>
<output id="result"></output>
<script>
    var worker = new Worker('worker.js');
    worker.onmessage = function (event) {
        document.getElementById('result').textContent += ', ' + event.data;
    };
    worker.postMessage({from:17,to:50});
</script>
</body>
</html>

и файл worker.js:

onmessage = function(event)
{
    var n = event.data.from;
    while (n < event.data.to) {
        n += 1;
        if(isPrime(n)){
            // found a prime!
            postMessage(n);
        }
    }
};

function isPrime(number){
    for (var i = 2; i <= Math.sqrt(number); i += 1){
        if (number % i == 0){
            return false;
        }
    }
    return true;
}

Для тех,  кто хочет поиграться с примером, – пожалуйста сюда.

Теперь немного комментариев.

var worker = new Worker('worker.js');

создаем объект Worker. Объект не запускается до полной загрузки и выполнения файла. Если путь к объекту Worker возвращает ошибку 404, его выполнение прекращается без уведомлений.

worker.onmessage = function (event) {

прописываем колбэк, который выполнится при вызове метода postMessage внутри воркера

worker.postMessage({from:17,to:50});

отправляем в воркер сообщение с параметрами диапазона ( мы хотим получить все простые числа в диапозоне с 17 до 50 )

Перейдем к коду воркера:

onmessage = function(event){

функция будет вызвана, при обращении к метод worker.postMessage() в основном скрипте

 var n = event.data.from;

доступ к данным внутри метода можно получить через event.data

 

Взаимодействие  с объектами Worker

Как уже было показано на примере, взаимодействие с воркерами осуществляется путем потоковой передачи сообщений (метод postMessage – для отправки и колбэк onmessage – для приема).

Колбэк на получение сообщения можно также навешивать следующим способом:

addEventListener('message', function(e) {

именно он является рекомендуемым для использования.

 

Ограничения Web Worker

К чему можно обращаться внутри worker-скрипта:

У worker-скрипта нет доступа к:

  • Модель DOM (она не ориентирована на многопоточное исполнение)
  • Объект window
  • Объект document
  • Объект parent

Методы postMessage и onmessage являются глобальными для ворвера, и к ним можно обратиться как на прямую, так и через this и self.

Максимальное количество workerов – 256, после чего стек переполняется и пишет ошибку:

Maximum number of Web Worker instances(256) exceeded for this window.

 

Отлов ошибок

Также, как и с сообщениями, мы можем подписаться на получение ошибок:

 worker.addEventListener('error', onError, false);

 

Динамическое создание worker-скриптов

Иногда возникает необходимо создать worker-скрипт динамически(!внимание именно worker-скрипт, а не сам worker), в зависимости от различных условий. В таком случае создание worker из отдельного файла нас не устраивает и мы должны воспользоваться вторым способом – через подготовленный объект Blob, а точнее – ссылки на него.

Вот так это будет выглядеть в коде

var script = "onmessage = function(e) { postMessage('msg from worker'); }";
var blob = new Blob([script]);
var blobURL = window.URL.createObjectURL(blob);
var worker = new Worker(blobURL);

Для удобства скрипт изначально пожно записать в script тег:

<script id="worker1" type="javascript/worker">
    //...код вашего воркера
</script>

а потом получить содержимое:

var script = document.querySelector('#worker1').textContent;

!Внимание: не забудьте поставить тип javascript/worker, это предотвратит разбор кода js-движком браузера.

 

Литература

К сожалению, примеров, которые бы показались мне интересными, не нашел. Сам сейчас работаю над более продвинутой версией получения больших простых чисел.