Под катом рассказ о том, как ими пользоваться, какие ограничения есть в воркерах и об особенностях взаимодействия с ними в разных браузерах.
Отдельный контекст для выполнения фоновых задач, который не блокирует UI. Обычно worker создаётся в виде отдельного скрипта, ресурсы worker-а живут в процессе создавшей его страницы. Shared worker — то же самое, но может быть использован с нескольких страниц.
В worker-е есть:
- navigator
- location
- applicationCache
- XHR, websocket
- importScripts для синхронной загрузки скриптов
Worker создаётся из отдельного скрипта:
var worker = new Worker(scriptUrl);
var sharedWorker = new SharedWorker(scriptUrl);
Shared worker идентифицируется по URL. Чтобы создать второй воркер из одного файла, можно добавить какой-нибудь параметр в URL (worker.js?num=2).
Worker можно создать и без отдельного файла. Например, так создать его из текста функции:
var code = workerFn.toString();
code = code.substring(code.indexOf("{")+1, code.lastIndexOf("}"));
var blob = new Blob([code], {type: 'application/javascript'});
worker = new Worker(URL.createObjectURL(blob));
Создать worker из worker-а можно только в Firefox. В Chrome можно создать shared worker из странички и передать его порт другому worker-у (об этом ниже).
DOM
В worker-е нельзя использовать DOM, вместо window глобальный объект называется self. Нельзя получить доступ к localStorage и рисовать на canvas. Такие же ограничения обычно есть во всех десктопных API: доступ к окнам только из UI-треда.
Доступ к объектам
Из worker-ов нельзя вернуть объект. В javascript нет lock-ов и других возможностей потокобезопасности, поэтому из worker-ов нельзя передавать объекты по ссылке, всё отправленное в worker или из него будет скопировано.
CORS
Пока что worker-ы не поддерживают CORS, создать worker можно только загрузив его со своего домена.
Размер стека
Для worker-ов выделяется меньший размер стека, иногда это имеет значение:
Chrome/osx | Firefox/osx | Safari/osx | Chrome/win | Firefox/win | IE11/win | |
---|---|---|---|---|---|---|
web | 20 800 | 48 000 | 63 000 | 41 900 | 51 000 | 63 000 |
worker | 5 300 | 43 300 | 6 100 | 21 300 | 37 000 | 30 100 |
console
До недавнего времени не было, но обычно сейчас уже есть. В некоторых браузерах консоли в worker-ах нет, поэтому перед обращением лучше проверить её доступность.
После создания worker-а ему можно отправить сообщение:
worker.postMessage({hello: 'world'});
worker.onmessage = function(e) { e.data ... };
sharedWorker.port.postMessage({hello: 'world'});
sharedWorker.port.onmessage = function(e) { e.data... };
Подписаться на сообщение в worker-е так:
// worker
self.onmessage = function(e) { e.data... };
// shared worker
self.onconnect = function(e) {
var port = e.ports[0];
port.onmessage = function(e) { e.data... };
};
Аналогично и обратно, из worker-а можно вызвать или self.postMessage, или port.postMessage для shared worker-ов.
Метод postMessage использует алгоритм structured clone для клонирования объектов. Это не то же самое, что сериализация в JSON. Алгоритм умеет:
- копировать RegExp, Blob, File, ImageData
- восстанавливать циклические ссылки
Но не умеет:
- Error, Function, DOM-элементы (упадёт ошибка)
- свойства и прототипы (они не склонируются)
Transferables
Передавать по ссылке кое-что таки можно. Для этого существует второй параметр в postMessage, transferList:
var ab = new ArrayBuffer(size);
worker.postMessage({ data: ab }, [ab]);
В transferList можно передать список объектов, которые будут перемещены. Поддерживаются только ArrayBuffer и MessagePort. В вызывающем контексте объект будет очищен (neutered): у ArrayBuffer будет нулевая длина, и попытка его повторной отправки приведёт к ошибке:
Uncaught DOMException: Failed to execute 'postMessage' on 'Worker': An ArrayBuffer is neutered and could not be cloned.
В Firefox можно создать worker из worker-а (стандарт определяет subworker-ы).
Сейчас в хроме нельзя создать worker из worker-а, а иногда worker-ам надо взаимодействовать между собой. Самый простой способ — сделать передачу сообщений от одного к другому через код страницы. Но это неудобно, потому что: 1. надо писать дополнительный код, 2. в 2 раза увеличивает количество взаимодействий и копирования данных, 3. требует выполнения кода в UI-контексте.
Worker можно научить общаться с shared worker-ом, передав ему порт shared worker-а, при этом передаваемый порт в UI-контексте мы теряем; если он нужен, надо будет переподключиться к shared worker-у, создав его заново. Передача порта выглядит так:
worker.postMessage({ port: sharedWorker.port }, [sharedWorker.port]);
// в worker-е поймать этот порт и сделать что-то с ним
Правда для синхронизации всё равно движком V8 используется UI-контекст, в чём можно убедиться, завесив страничку на какое-то время: worker-ы продолжают работать, а postMessage между ними не ходят, ожидая особождения UI-контекста.
Производительность разная для нескольких случаев, разных размеров данных и использование transferList (trlist):
- dedicated worker
- shared worker в создавшем процессе
- shared worker в другом процессе
В таблице показано количество циклов пересылки данных от worker и обратно в секунду.
Chrome/osx | FF/osx | Safari/osx | Chrome/win | FF/win | IE11/win | |
---|---|---|---|---|---|---|
dedicated:10B | 9 300 | 8 400 | 21 000 | 6 800 | 7 300 | 3 200 |
dedicated:10kB | 4 000 | 7 000 | 5 000 | 3 000 | 5 000 | 1 800 |
dedicated:1MB | 80 | 500 | 90 | 60 | 400 | 200 |
dedicated:10MB | 8 | 40 | 7 | 7 | 52 | 30 |
dedicated:trlist:10MB | 8 400 | 1 100 | 2 500 | 6 200 | 1 900 | 2 200 |
shared:10B | 3 100 | 8 300 | - | 2 200 | 5 500 | - |
shared:10kB | 1 800 | 6 900 | - | 1 400 | 4 500 | - |
shared:1MB | 40 | 500 | - | 32 | 400 | - |
shared:10MB | 4 | 40 | - | 4 | 53 | - |
shared:trlist:10MB | - | 260 | - | - | 1 800 | - |
shared-ipc:10B | 3 000 | - | - | 2 700 | - | - |
shared-ipc:10kB | 1 600 | - | - | 1 700 | - | - |
shared-ipc:1MB | 40 | - | - | 30 | - | - |
shared-ipc:10MB | 4 | - | - | 3 | - | - |
Выводы, которые можно сделать из данных:
- затраты на взаимодействие с dedicated worker в хроме меньше, чем с shared;
- большие объёмы данных намного быстрее передавать через transferList;
- но всё-таки передача transferList не эквивалентна отправке ссылки или несколких байт.
Decicated worker можно убить, вызвав worker.terminate(). С shared worker так нельзя, его выполнение будет прекращено:
- когда он закроется сам, вызвав self.close()
- когда закроются все странички, его использующие (при этом у worker-а не будет возможности закончить вычисления)
- когда пользователь принудительно завершит его (например, в хроме из chrome://inspect)
- когда упадёт или он, или процесс странички, где он живёт
Попробуем вызвать крэш процесса из shared worker-а. Вместе с worker-ом, конечно, упадёт и создавшая его вкладка. Во вкладке, где он ещё использовался, увидим такое сообщение:
К сожалению, сейчас нет штатного способа отследить закрытие worker-а или страницы, его использующей.
SharedWorker живёт процессе в страницы, создавшей его. На неё учитывается и показывается в task manager CPU и память, которые потребляет worker. Если страничку закроют, её процесс с worker-ом отдаст память, используемую страницей (не сразу, через некоторое время после закрытия) и останется жить, пока другие страницы используют этот worker. Интересно, что при этом такой процесс полностью исчезнет из статистики хрома: ни память, ни CPU пользоваель не сможет отследить в его внутреннем task manager-е. Это неприятно, т.к. пользователь скорее всего не догадается, почему браузер стал потреблять так много ресурсов.
В chrome shared worker-ы доступны на страничке chrome://inspect/#workers:
Именно туда пишется вывод console из worker.
Dedicated worker в хроме и IE отлаживается в страничке, на которой он выполняется:
В других браузерах с отладкой worker-ов пока что плохо.
Поддержка разных worker-ов на Can I Use. Коротко, применительно к сегодняшнему вебу: worker есть на современных браузерах, sharedworker — на продвинутых десктопных браузерах, serviceworker — пока что рано.
Всё написанное актуально на лето 2015 года, не забывайте, что веб быстро меняется.
Using Web Workers (MDN)
The Basics of Web Workers
Living Standard: Web workers
Transferable Objects
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий