Multithreading in JavaScript
Alex Saskevich, @asaskevich
for (var i = 0; i < 10e10; i ++) {
doSomeAwesomeWork();
}
Страницу можно "подвесить" вот так:
Или вот так:
var doSomeWork = function() {
doSomeHardCalculations();
}
function someThread(id) {
setInterval(function() {
console.log('Hi from thread #id =', id);
}, 20);
}
someThread(1);
someThread(2);
someThread(3);
someThread(4);
someThread(5);
Один из вариантов реализации "потоков"
Использование setTimeout для реализации асинхронных вычислений.
var doCalculations = function(n) {
setTimeout(function() {
var i = 0;
var res = 0;
while (i < n) {
res += i;
i++;
}
}, 0);
}
Web Workers помогут в реализации многопоточности!
Доступность Web Workers в современных браузерах:
var worker = new Worker('worker.js');
//..
var worker = new Worker('scripts/worker.js');
Код воркера должен быть расположен в отдельном файле.
worker.postMessage('hi');
worker.addEventListener('message', function(e) {
console.log('Worker said: ', e.data);
}, false);
Взаимодействие потоков между собой:
Примерная схема взаимодействия потоков:
// Передавать можно либо строки,
worker.postMessage('Hello World');
// либо JSON объекты
worker.postMessage({msg: 'say hello'});
Что можно передать в другой поток:
self.addEventListener('message', function(e) {
self.postMessage(e.data);
}, false);
self для веб-воркера имеет тот же смысл, что и this для основного скрипта
// Основной скрипт
worker.terminate();
...
// worker.js
self.close();
Как остановить созданный поток?
Объект navigator
Объект location (read-only)
XMLHttpRequest
setTimeout() / clearTimeout()
setInterval() / clearInterval()
Кэш приложений
Импорт внешних скриптов с использованием метода importScripts()
Создание новых воркеров
Web Workers имеют доступ к:
Web Workers не могут работать с:
-
Моделью DOM
-
document
-
window
-
parent
// worker.js
// Ничего не загрузит
importScripts();
// Загрузит script.js
importScripts('script.js');
// Загрузит foo.js и bar.js
importScripts('foo.js', 'bar.js');
Импорт внешних скриптов
// Создаем на основе BlobBuilder
// экземпляр встроенного воркера
var code = 'onmessage = function(e) { ... }';
var blob = new BlobBuilder();
blob.append(code);
blob = blob.getBlob();
Встроенный воркер при помощи BlobBuilder
// Создаем на основе Blob
// экземпляр встроенного воркера
var code = 'onmessage = function(e) { ... }';
var blob = new Blob([code],
{type: 'application/javascript'});
Встроенный воркер при помощи Blob *
* Chrome 8+, Firefox 6+, Safari 6.0+, Opera 15+
var blobURL = URL.createObjectURL(blob);
var worker = new Worker(blobURL);
Создание воркера на основе Blob
// blobURL имеет примерно такой вид
// a923da41-3ce9-4d1a-f145-eacs532539b1
URL.revokeObjectURL(blobURL);
Удалить ненужный Blob можно так:
<script id="worker" type="javascript/worker">
// Код воркера
// ...
</script>
// ...
var code = document.querySelector('#worker').textContent;
var blob = new Blob([code],
{type: 'application/javascript'});
Конечный пример создания встроенного воркера
function errorHandler(e) {
// e.lineno - номер строки
// e.filename - имя файла
// e.message - сообщение об ошибке
// ...
}
worker.addEventListener('error', errorHandler, false);
Обработка ошибок
Немного о безопасности
-
Скрипты и страница вызова должны относиться к одному источнику
-
Объекты Worker не запускаются локально
-
* только для Chrome
-
--allow-file-access-from-files
-
Shared Workers
var worker = new SharedWorker("script.js");
Создание объекта SharedWorker
worker.port.addEventListener('message',
function(e) { .. }, false);
Обработка событий:
worker.port.start();
Запуск Shared Worker
worker.port.postMessage('hi');
Посылка сообщений внутрь потока
var ports = [];
onconnect = function(event) {
var port = event.ports[0];
ports.push(port);
port.start();
port.addEventListener("message",
function(e) { handler(e, port); } );
}
handler = function(e, port) {
port.postMessage('hello');
}
Примерная реализация SharedWorkera
Применение Web Workerов
-
Кеширование данных
-
Проверка правописания
-
Обработка аудио/видео
-
Фоновые операции ввода/вывода
-
Работа с веб-службами
-
Обработка больших объемов данных
-
И так далее...
Имитация Web Workers в устаревших браузерах
fakeworker-js
Имитирует многопоточность при помощи setTimeout() & eval()
Удобен для отладки веб-воркеров
Simulated Web Workers
Создает невидимый iframe, внутри которого и работает скрипт воркера
ie-web-worker
Симулирует Web Workers в IE
Аналоги, реализующие многопоточность
Multithread.js
Начало работы
<script src="path/to/multithread.js"></script>
var threadCount = 4;
var MT = new Multithread(threadCount);
MT.process(function(a, b, c) {
// некоторые вычисления
return result;
}, function callback(result) {
// обработка результата
})(a, b, c);
Асинхронный запуск функции
function scopeCheck() {
var scopeVar = 2;
MT.process(
// scopeVar is not defined
function() { return scopeVar + 2; },
function(r) { console.log('Hop-hey'); }
)();
}
scopeCheck();
Область видимости потока
Ограничения
-
Все данные, передаваемые в поток, должны быть JSON сериализуемы
-
Потоки не должны работать с DOM
Parallel.js
Установка
<script src="parallel.js"></script>
$ npm install paralleljs
Инициализация
var p = new Parallel(someData);
Запуск потока
var slowSquare = function (n) {
var i = 0;
while (++i < n * n) {}
return i;
};
var p = new Parallel(100000);
p.spawn(slowSquare).then(function() {
alert('Completed!');
});
map - последовательная обработка входных данных
var p = new Parallel([0, 1, 2, 3, 4, 5, 6]),
log = function () { console.log(arguments); };
function fib(n) {
return n < 2 ? 1 : fib(n - 1) + fib(n - 2);
};
p.map(fib).then(log)
reduce - свертка обработанных данных
var p = new Parallel(_.range(50));
function add(d) { return d[0] + d[1]; }
function log() { console.log(arguments); }
p.map(function (n) {
return Math.pow(10, n);
}).reduce(add).then(log);