JavaScript и темы
Есть ли способ сделать многопоточность в JavaScript?
14 ответов
См. http://caniuse.com/ для получения самой последней информации о поддержке.
Следующим было состояние поддержки около 2009 года.
Слова, которые вы хотите найти в Google, являются рабочими потоками JavaScript
Помимо Gears, сейчас ничего не доступно, но есть много разговоров о том, как это реализовать, поэтому я думаю, посмотрите на этот вопрос, поскольку ответ, без сомнения, изменится в будущем.
Вот соответствующая документация для Gears: WorkerPool API
У WHATWG есть проект Рекомендации для рабочих потоков: веб-работники
И есть также рабочие потоки DOM Mozilla
Обновление: июнь 2009 г., текущее состояние поддержки браузером потоков JavaScript
Firefox 3.5 имеет веб-работников. Несколько демонстраций веб-работников, если вы хотите увидеть их в действии:
- Имитация отжига (ссылка "Попробуйте")
- Space Invaders (ссылка в конце поста)
- MoonBat JavaScript Benchmark (первая ссылка)
Плагин Gears также можно установить в Firefox.
Safari 4 и ночные нити WebKit имеют рабочие потоки:
В Chrome встроены Gears, поэтому он может создавать потоки, хотя для этого требуется подтверждение от пользователя (и он использует другой API для веб-работников, хотя он будет работать в любом браузере с установленным плагином Gears):
- Демонстрация Google Gears WorkerPool (не очень хороший пример, поскольку он работает слишком быстро для тестирования в Chrome и Firefox, хотя IE запускает его достаточно медленно, чтобы блокировать взаимодействие)
IE8 и IE9 могут создавать потоки только с установленным плагином Gears
Другой способ сделать многопоточность и асинхронность в JavaScript
До HTML5 JavaScript допускал выполнение только одного потока на страницу.
Был какой-то хакерский способ симулировать асинхронное выполнение с Yield, setTimeout()
, setInterval()
, XMLHttpRequest
или обработчики событий (см. в конце этого поста пример с yield и setTimeout()
).
Но с HTML5 теперь мы можем использовать рабочие потоки для распараллеливания выполнения функций. Вот пример использования.
Реальная многопоточность
Многопоточность: рабочие потоки JavaScript
HTML5 представил веб-рабочие потоки (см.: совместимость браузеров)
Примечание: IE9 и более ранние версии не поддерживают его.
Эти рабочие потоки являются потоками JavaScript, которые работают в фоновом режиме, не влияя на производительность страницы. Для получения дополнительной информации о Web Worker прочитайте документацию или этот учебник.
Вот простой пример с 3 потоками Web Worker, которые считают MAX_VALUE и показывают текущее вычисленное значение на нашей странице:
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackru.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }
var MAX_VALUE = 10000;
/*
* Here are the workers
*/
//Worker 1
var worker1 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
//We add a listener to the worker to get the response and show it in the page
worker1.addEventListener('message', function(e) {
document.getElementById("result1").innerHTML = e.data;
}, false);
//Worker 2
var worker2 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker2.addEventListener('message', function(e) {
document.getElementById("result2").innerHTML = e.data;
}, false);
//Worker 3
var worker3 = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = 0;
while(value <= e.data){
self.postMessage(value);
value++;
}
}, false);
}));
worker3.addEventListener('message', function(e) {
document.getElementById("result3").innerHTML = e.data;
}, false);
// Start and send data to our worker.
worker1.postMessage(MAX_VALUE);
worker2.postMessage(MAX_VALUE);
worker3.postMessage(MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Мы видим, что три потока выполняются в параллельном режиме и печатают их текущее значение на странице. Они не замораживают страницу, потому что они выполняются в фоновом режиме с разделенными потоками.
Многопоточность: с несколькими фреймами
Другой способ добиться этого - использовать несколько фреймов, каждый из которых будет выполнять поток. Мы можем дать iframe некоторые параметры по URL, и iframe может связаться со своим родителем, чтобы получить результат и распечатать его обратно (iframe должен находиться в том же домене).
Этот пример работает не во всех браузерах! iframes обычно работают в том же потоке / процессе, что и главная страница (но Firefox и Chromium, кажется, обрабатывают это по-разному).
Так как фрагмент кода не поддерживает несколько файлов HTML, я просто предоставлю различные коды здесь:
index.html:
//The 3 iframes containing the code (take the thread id in param)
<iframe id="threadFrame1" src="thread.html?id=1"></iframe>
<iframe id="threadFrame2" src="thread.html?id=2"></iframe>
<iframe id="threadFrame3" src="thread.html?id=3"></iframe>
//Divs that shows the result
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
<script>
//This function is called by each iframe
function threadResult(threadId, result) {
document.getElementById("result" + threadId).innerHTML = result;
}
</script>
thread.html:
//Get the parameters in the URL: http://stackru.com/a/1099670/2576706
function getQueryParams(paramName) {
var qs = document.location.search.split('+').join(' ');
var params = {}, tokens, re = /[?&]?([^=]+)=([^&]*)/g;
while (tokens = re.exec(qs)) {
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]);
}
return params[paramName];
}
//The thread code (get the id from the URL, we can pass other parameters as needed)
var MAX_VALUE = 100000;
(function thread() {
var threadId = getQueryParams('id');
for(var i=0; i<MAX_VALUE; i++){
parent.threadResult(threadId, i);
}
})();
Имитация многопоточности
Однопоточный: эмулируйте параллелизм JavaScript с помощью setTimeout()
"Наивным" способом было бы выполнить функцию setTimeout()
один за другим вот так:
setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]
Но этот метод не работает, потому что каждая задача будет выполняться одна за другой.
Мы можем симулировать асинхронное выполнение, вызывая функцию рекурсивно так:
var MAX_VALUE = 10000;
function thread1(value, maxValue){
var me = this;
document.getElementById("result1").innerHTML = value;
value++;
//Continue execution
if(value<=maxValue)
setTimeout(function () { me.thread1(value, maxValue); }, 0);
}
function thread2(value, maxValue){
var me = this;
document.getElementById("result2").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread2(value, maxValue); }, 0);
}
function thread3(value, maxValue){
var me = this;
document.getElementById("result3").innerHTML = value;
value++;
if(value<=maxValue)
setTimeout(function () { me.thread3(value, maxValue); }, 0);
}
thread1(0, MAX_VALUE);
thread2(0, MAX_VALUE);
thread3(0, MAX_VALUE);
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
Как вы можете видеть, этот второй метод очень медленный и замораживает браузер, потому что он использует основной поток для выполнения функций.
Однопоточный: эмуляция JavaScript-параллелизма с помощью yield
Выход является новой функцией в ECMAScript 6, он работает только на самой старой версии Firefox и Chrome (в Chrome вам нужно включить экспериментальный JavaScript, появляющийся в chrome: // flags / # enable-javascript-harmony).
Ключевое слово yield приводит к приостановке выполнения функции генератора, и значение выражения, следующего за ключевым словом yield, возвращается вызывающей стороне генератора. Его можно рассматривать как генераторную версию ключевого слова return.
Генератор позволяет приостановить выполнение функции и возобновить ее позже. Генератор может быть использован для планирования ваших функций с техникой, называемой прыжками на батуте.
Вот пример:
var MAX_VALUE = 10000;
Scheduler = {
_tasks: [],
add: function(func){
this._tasks.push(func);
},
start: function(){
var tasks = this._tasks;
var length = tasks.length;
while(length>0){
for(var i=0; i<length; i++){
var res = tasks[i].next();
if(res.done){
tasks.splice(i, 1);
length--;
i--;
}
}
}
}
}
function* updateUI(threadID, maxValue) {
var value = 0;
while(value<=maxValue){
yield document.getElementById("result" + threadID).innerHTML = value;
value++;
}
}
Scheduler.add(updateUI(1, MAX_VALUE));
Scheduler.add(updateUI(2, MAX_VALUE));
Scheduler.add(updateUI(3, MAX_VALUE));
Scheduler.start()
<div id="result1"></div>
<div id="result2"></div>
<div id="result3"></div>
С HTML5 "side-specs" больше не нужно взламывать javascript с помощью setTimeout(), setInterval() и т. Д.
HTML5 & Friends представляет спецификацию веб-работников javascript. Это API для асинхронного и независимого запуска скриптов.
Ссылки на спецификацию и учебник.
В Javascript нет настоящей многопоточности, но вы можете получить асинхронное поведение, используя setTimeout()
и асинхронные запросы AJAX.
Что именно вы пытаетесь достичь?
Там нет истинного потока в JavaScript. JavaScript, будучи податливым языком, действительно позволяет вам подражать некоторым из них. Вот пример, с которым я столкнулся на днях.
Вот только способ симулировать многопоточность в Javascript
Теперь я собираюсь создать 3 потока, которые будут вычислять сложение чисел, числа можно разделить на 13, а числа можно разделить на 3 до 10000000000. И эти 3 функции не могут работать одновременно с тем, что означает параллелизм. Но я покажу вам трюк, который заставит эти функции работать рекурсивно в одно и то же время: jsFiddle
Этот код принадлежит мне.
Часть тела
<div class="div1">
<input type="button" value="start/stop" onclick="_thread1.control ? _thread1.stop() : _thread1.start();" /><span>Counting summation of numbers till 10000000000</span> = <span id="1">0</span>
</div>
<div class="div2">
<input type="button" value="start/stop" onclick="_thread2.control ? _thread2.stop() : _thread2.start();" /><span>Counting numbers can be divided with 13 till 10000000000</span> = <span id="2">0</span>
</div>
<div class="div3">
<input type="button" value="start/stop" onclick="_thread3.control ? _thread3.stop() : _thread3.start();" /><span>Counting numbers can be divided with 3 till 10000000000</span> = <span id="3">0</span>
</div>
Часть Javascript
var _thread1 = {//This is my thread as object
control: false,//this is my control that will be used for start stop
value: 0, //stores my result
current: 0, //stores current number
func: function () { //this is my func that will run
if (this.control) { // checking for control to run
if (this.current < 10000000000) {
this.value += this.current;
document.getElementById("1").innerHTML = this.value;
this.current++;
}
}
setTimeout(function () { // And here is the trick! setTimeout is a king that will help us simulate threading in javascript
_thread1.func(); //You cannot use this.func() just try to call with your object name
}, 0);
},
start: function () {
this.control = true; //start function
},
stop: function () {
this.control = false; //stop function
},
init: function () {
setTimeout(function () {
_thread1.func(); // the first call of our thread
}, 0)
}
};
var _thread2 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 13 == 0) {
this.value++;
}
this.current++;
document.getElementById("2").innerHTML = this.value;
}
setTimeout(function () {
_thread2.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread2.func();
}, 0)
}
};
var _thread3 = {
control: false,
value: 0,
current: 0,
func: function () {
if (this.control) {
if (this.current % 3 == 0) {
this.value++;
}
this.current++;
document.getElementById("3").innerHTML = this.value;
}
setTimeout(function () {
_thread3.func();
}, 0);
},
start: function () {
this.control = true;
},
stop: function () {
this.control = false;
},
init: function () {
setTimeout(function () {
_thread3.func();
}, 0)
}
};
_thread1.init();
_thread2.init();
_thread3.init();
Я надеюсь, что этот путь будет полезным.
Вы можете использовать Narrative JavaScript, компилятор, который преобразует ваш код в конечный автомат, эффективно позволяя вам эмулировать потоки. Это достигается путем добавления в язык оператора "выдачи" (обозначается как "->"), который позволяет писать асинхронный код в одном блоке линейного кода.
Новый двигатель v8, который должен выйти сегодня, поддерживает его (я думаю)
Другой возможный метод - использование интерпретатора javascript в среде javascript.
Создав несколько интерпретаторов и управляя их выполнением из основного потока, вы можете имитировать многопоточность с каждым потоком, работающим в его собственной среде.
Подход несколько похож на веб-работников, но вы предоставляете переводчику доступ к глобальной среде браузера.
Я сделал небольшой проект, чтобы продемонстрировать это.
Более подробное объяснение в этом блоге.
В необработанном Javascript лучшее, что вы можете сделать, - это использовать несколько асинхронных вызовов (xmlhttprequest), но это не очень многопоточно и очень ограничено. Google Gears добавляет в браузер ряд API, некоторые из которых можно использовать для поддержки многопоточности.
Если вы не можете или не хотите использовать какой-либо материал AJAX, используйте iframe или десять!;) Вы можете запускать процессы в iframe параллельно с главной страницей, не беспокоясь о проблемах, сравнимых с кросс-браузерными, или проблемах синтаксиса с точечной сетью AJAX и т. Д., И вы можете вызывать JavaScript главной страницы (включая импортированный ею JavaScript) из IFrame.
Например, в родительском iframe, чтобы позвонить egFunction()
в родительском документе после загрузки содержимого iframe (это асинхронная часть)
parent.egFunction();
Динамически генерируйте iframes, так что основной HTML-код свободен от них, если хотите.
У Javascript нет потоков, но у нас есть работники.
Рабочие могут быть хорошим выбором, если вам не нужны общие объекты.
Большинство реализаций браузера фактически распределяют работников по всем ядрам, позволяя вам использовать все ядра. Вы можете увидеть демо этого здесь.
Я разработал библиотеку под названием task.js, которая делает это очень простым.
task.js Упрощенный интерфейс для запуска кода, интенсивно использующего процессор, для всех ядер (node.js и web)
Примером будет
function blocking (exampleArgument) {
// block thread
}
// turn blocking pure function into a worker task
const blockingAsync = task.wrap(blocking);
// run task on a autoscaling worker pool
blockingAsync('exampleArgumentValue').then(result => {
// do something with result
});
Со спецификацией HTML5 вам не нужно писать слишком много JS для того же самого или находить некоторые хаки.
Одна из функций, представленных в HTML5, - это Web Workers - это JavaScript, работающий в фоновом режиме, независимо от других скриптов, не влияющий на производительность страницы.
Поддерживается практически во всех браузерах:
Chrome - 4.0+
IE - 10.0+
Мозилла - 3.5+
Сафари - 4.0+
Опера - 11,5+
Topaz - это безблокирующий многопоточный движок Javascript для .NET: https://github.com/koculu/topaz