JavaScript и темы

137

Есть ли способ сделать многопоточность в JavaScript?

Нияз
источник
2
JavaScript не содержит потоков, по крайней мере, в его текущей форме. Что именно ты пытаешься сделать?
Эрик Пол
3
Можете ли вы изменить принятый ответ на этот? stackoverflow.com/a/30891727/2576706 Это гораздо более развито для будущего пользователя ..
Людовик Фельц

Ответы:

109

См. Http://caniuse.com/#search=worker для получения самой последней информации о поддержке.

Следующим было состояние поддержки около 2009 года.


Слова, которые вы хотите найти в Google, являются рабочими потоками JavaScript

Помимо Gears, сейчас ничего не доступно, но есть много разговоров о том, как это реализовать, поэтому я думаю, посмотрите на этот вопрос, поскольку ответ, без сомнения, изменится в будущем.

Вот соответствующая документация для Gears: WorkerPool API

WHATWG предлагает черновой вариант рекомендации для рабочих потоков: веб-работники

И есть также рабочие потоки DOM Mozilla


Обновление: июнь 2009 г., текущее состояние поддержки браузером потоков JavaScript

Firefox 3.5 имеет веб-работников. Несколько демонстраций веб-работников, если вы хотите увидеть их в действии:

Плагин Gears также можно установить в Firefox.

Safari 4 и ночные нити WebKit имеют рабочие потоки:

В Chrome встроены Gears, поэтому он может создавать потоки, хотя для этого требуется подтверждение от пользователя (и он использует другой API для веб-работников, хотя он будет работать в любом браузере с установленным плагином Gears):

  • Демонстрация Google Gears WorkerPool (не очень хороший пример, поскольку он работает слишком быстро для тестирования в Chrome и Firefox, хотя IE запускает его достаточно медленно, чтобы блокировать взаимодействие)

IE8 и IE9 могут создавать потоки только с установленным плагином Gears

Сэм Хаслер
источник
1
Хотя Safari 4 поддерживает веб-работников, похоже, что только Firefox поддерживает передачу сложных объектов через postMessage: hacks.mozilla.org/2009/07/working-smarter-not-harder См. Последний абзац этого поста об использовании в реальном мире в проекте Bespin для ссылок на шим, который реализует Worker API в терминах Google Gears и который добавляет недостающие функции в рабочую реализацию Safari 4, а также подробности того, как они реализовали прозрачные настраиваемые события поверх интерфейса postMessage.
Сэм Хаслер
6
Теперь IE9 вышел, вы можете обновить «IE8 может создавать потоки только с установленным плагином Gears» до «IE8 и IE9 может
создавать
2
@ inf3rno для длительных вычислений в другом потоке, чтобы они не замедляли работу браузера.
Сэм Хаслер
6
@ SamHasler Вы можете пересмотреть свой ответ. Веб-работники теперь поддерживаются всеми современными настольными браузерами. См. Также caniuse.com/#search=worker
Роб W
2
@ SamHasler также стоит отметить, что Google Gears больше не поддерживается.
skeggse
73

Другой способ сделать многопоточность и асинхронность в JavaScript

До HTML5 JavaScript допускал выполнение только одного потока на страницу.

Был некоторый Hacky способ имитации асинхронного исполнения с Выход , setTimeout(), setInterval(), XMLHttpRequestили обработчики событий (см в конце этого поста для примера с выходом и 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://stackoverflow.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://stackoverflow.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>

Людовик Фельц
источник
3
Это действительно должен быть лучший ответ.
Джерри Лю
14

С HTML5 "side-specs" больше не нужно взламывать javascript с помощью setTimeout (), setInterval () и т. Д.

HTML5 & Friends представляет спецификацию веб-работников javascript . Это API для асинхронного и независимого запуска скриптов.

Ссылки на спецификацию и учебник .

djondal
источник
11

Там нет истинного потока в JavaScript. JavaScript, будучи гибким языком, действительно позволяет вам эмулировать некоторые из них. Вот пример, с которым я столкнулся на днях.

Свен
источник
1
Что вы подразумеваете под "истинным потоком"? Зеленые нити - это настоящие нити.
Уэс
10

В Javascript нет настоящей многопоточности, но вы можете получить асинхронное поведение, используя setTimeout()и асинхронные запросы AJAX.

Что именно вы пытаетесь достичь?

17 из 26
источник
7

Вот только способ симулировать многопоточность в 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();

Я надеюсь, что этот путь будет полезным.

Ахмет Кан Гювен
источник
6

Вы можете использовать Narrative JavaScript , компилятор, который преобразует ваш код в конечный автомат, эффективно позволяя вам эмулировать потоки. Это делается путем добавления оператора «выдачи» (обозначается как «->») к языку, который позволяет писать асинхронный код в одном линейном блоке кода.

Антуан Обри
источник
3

В необработанном Javascript лучшее, что вы можете сделать, это использовать несколько асинхронных вызовов (xmlhttprequest), но это не очень многопоточность и очень ограничено. Google Gears добавляет в браузер ряд API, некоторые из которых можно использовать для поддержки многопоточности.

jsight
источник
1
Google Gears API больше не доступен.
Людовик Фельц
3

Если вы не можете или не хотите использовать какой-либо материал AJAX, используйте iframe или десять! ;) У вас могут быть процессы, запущенные в iframes параллельно с главной страницей, не беспокоясь о проблемах, сравнимых с кроссбраузерными или синтаксическими проблемами с точечной сетью AJAX и т. Д., И вы можете вызывать JavaScript главной страницы (включая импортированный ею JavaScript) из IFrame.

Например, в родительском iframe вызывать egFunction()родительский документ после загрузки содержимого iframe (это асинхронная часть)

parent.egFunction();

Динамически генерируйте iframes, так что основной HTML-код свободен от них, если хотите.

Jono
источник
1
Это описание было слишком кратким, на мой взгляд. Не могли бы вы уточнить, как сделать эту технику, или опубликовать ссылку на учебник, показывающий некоторый код?
oligofren
3

Другой возможный метод - использование интерпретатора javascript в среде javascript.

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

Подход несколько похож на веб-работников, но вы предоставляете переводчику доступ к глобальной среде браузера.

Я сделал небольшой проект, чтобы продемонстрировать это .

Более подробное объяснение в этом посте .

Амитай Добо
источник
1

У 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
});
Чад Скира
источник
0

Со спецификацией HTML5 вам не нужно писать слишком много JS для того же самого или находить некоторые хаки.

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

Поддерживается практически во всех браузерах:

Chrome - 4.0+

IE - 10.0+

Мозилла - 3.5+

Сафари - 4.0+

Опера - 11,5+

Мандип Сингх
источник