Многопоточное выполнение кода с помощью Web Workers

Страницы Web становятся все в большей степени перегружены JavaScript, иногда в такой степени, что не могут двигаться. Вездесущность JavaScript является благом для разработчиков, но это означает, что язык может выполняться на широком множестве устройств, включая такие, которым не хватает мощности для сегодняшних приложений Web. Существует несколько способов оптимизации JavaScript, но он все равно не будет работать так же быстро, как машинный код.

Превращение JavaScript в мультипоточный язык потребует значительных архитектурных изменений и фундаментального переосмысливания, поэтому Web Workers предлагают способ обойти эту проблему, позволяя расширить язык таким образом, что он может казаться в некоторых случаях мультипоточным. Другими словами может эффективно выполняться более одного процесса, но с некоторыми ограничениями. На самом деле достаточно много ограничений, поэтому они будут полезны только в определенных ситуациях.

Web Workers могут делать только одну вещь, но они делают ее очень хорошо. Они прекрасно справляются с выполнением быстрых вычислений, но не могут сделать более сложную работу, такую как доступ к DOM. Если сравнить web-приложение с кухней, то основной поток JavaScript будет шеф-поваром, собирающимся приготовить омлет. Если он делает все самостоятельно, он должен взбить яйцо, подготовить сковородку, растопить масло и, наконец, приготовить омлет. Если он хочет повысить эффективность, он может получить помощь от кухонного работника. Работник может взбить яйцо, позволяя шеф-повару подготовить сковороду и масло, и затем приготовить омлет. Работнику не разрешается прикасаться к сковороде или готовить омлет – он просто выполняет задачу, в то время как шеф-повар продолжает с другой работой.

Если бы Web Workers мог готовить, вот как бы он помог приготовить омлет

Использование Web Workers такое же. Если JavaScript содержит какие-то интенсивные вычисления с ресурсами, можно передать это Web Worker для обработки, в то время как основной процесс продолжает выполняться. Можно использовать более одного Web Worker, и Web Worker может делать более одной задачи.

Работник (worker) сам является просто некоторым кодом JavaScript в своем собственном файле. Также как концепция Web Workers является выполнением кода в отдельном потоке, так и код самого работника должен находиться в отдельном файле, или нескольких файлах, если используется больше одного работника. В нашем примере давайте начнем с создания пустого текстового файла, который назовем worker.js.

В нашем основном потоке JavaScript мы используем работника, создавая новый объект Worker:

Основной поток JavaScript

var worker = new Worker('worker.js');
Как и с кухонным помощником, мы передаем работнику что-то, он делает что-то с этим в фоновом режиме, и затем что-то нам возвращает. Коммуникация с работником осуществляется с помощью метода postMessage:

Основной поток JavaScript

// Создаем новый объект работника 
var worker = new Worker('worker.js');

// Посылаем простое сообщение, чтобы запустить работника 
worker.postMessage();

Можно также передать работнику переменную:

Основной поток JavaScript

// Создаем новый объект работника
var worker = new Worker('worker.js');

// Посылаем сообщение, чтобы запустить работника 
// и передаем ему переменную 
var info = 'Web Workers';
worker.postMessage(info);

В работнике, т.е. внутри файла worker.js, мы используем событие onmessage для получения сообщения из основного потока и выполнения какой-то работы. Если передается переменная, то можно получить к ней доступ с помощью event.data следующим образом:

worker.js
// Получаем сообщение из основного потока 
onmessage = function(event) {
// Выполняем что-то 
var info = event.data;
};

Отправка сообщений из работника назад в основной поток использует те же методы:

worker.js
// Получаем сообщение из основного потока 
onmessage = function(event) {
// Выполняем что-то 
var info = event.data;
};

Main JavaScript thread
// Создать новый объект worker 
var worker = new Worker('worker.js');

// Послать сообщение, чтобы запустить работника и
// передать ему переменную 
var info = 'Web Workers';
worker.postMessage(info);

// Получить сообщение от работника
worker.onmessage = function (event) {
// Сделать что-то
alert(event.data);
};


Opera создана как однопоточный браузер с поддержкой множества платформ, поэтому текущая реализация Web Workers разделяет выполнение кода в одном потоке UI. Однако другие браузеры могут иметь мультипоточную архитектуру, которая позволяет одновременное выполнение различных потоков кода.

Когда вы задаете работникам более сложные задачи, такие как управление большими массивами, или вычисление точек в трехмерном пространстве для отображения в основном потоке, то это становится очень мощным средством. Основное, что надо помнить, однако, состоит в том, что работники не могут получить доступ к DOM. В примере выше, например, мы не можем вызвать внутри работника alert() или даже document.getElementById() - он может только получать и возвращать переменные, хотя это могут быть строки, массивы, объекты JSON, и т.д.

Вот сводка того, что доступно и недоступно для Web Workers.

Могут использовать:
  • объект navigator
  • объект location (только чтение)
  • метод importScripts() (для доступа к файлам сценариев в том же домене)
  • объекты JavaScript, такие как Object, Array, Date, Math, String
  • XMLHttpRequest
  • методы setTimeout() и setInterval()

Не могут использовать:
  • DOM
  • Порождающую работника страницу (только через postMessage() )

Чтобы определить, поддерживает ли браузер пользователя Web Workers, можно проверить существование свойства Worker объекта window:

// Проверка, что имеется поддержка Web Workers 
if (!!window.Worker) {
// Ура, можно передать утомительную работу!
}

Web Workers особенно подходят в тех ситуациях, где нежелательно заставлять пользователя ждать, пока выполняется какой-то код. Основной поток вычислений может сконцентрироваться на работе с UI, отображая его как можно быстрее, в то время как Web Workers могут в фоновом режиме обрабатывать данные, используя AJAX для коммуникации с сервером.

--