JavaScript і теми


137

Чи є спосіб зробити багатопотокове в JavaScript?


2
JavaScript не містить потоків, принаймні у своєму поточному вигляді. Що саме ви намагаєтеся зробити?
Ерік Поль

3
Чи можете ви змінити прийняту відповідь на цю? stackoverflow.com/a/30891727/2576706 Це набагато розвиненіше для майбутнього користувача ..
Людовик Фельц

Відповіді:


109

Дивіться http://caniuse.com/#search=worker для найновішої інформації про підтримку.

Далі був стан підтримки близько 2009 року.


Слова, для яких потрібно перейти в Google, - це Worker Threads

Окрім Gears, зараз немає нічого доступного, але є багато розмов про те, як це здійснити, тому я думаю, слідкуйте за цим питанням, оскільки відповідь, без сумніву, зміниться в майбутньому.

Ось відповідна документація для Gears: WorkerPool API

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

А ще є робочі нитки Mozilla DOM


Оновлення: червень 2009 р., Поточний стан підтримки браузера для потоків JavaScript

У Firefox 3.5 працюють веб-працівники. Деякі демонстрації веб-працівників, якщо ви хочете бачити їх у дії:

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

У сафарі 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 для посилань на лайм, який реалізує API Worker в термінах Google Gears, і який додає відсутніх функцій для робочої реалізації Safari 4 та детальну інформацію про те, як вони реалізували прозорі користувацькі події поверх інтерфейсу PostMessage.
Сем Хаслер

6
Тепер IE9 вийшов, ви можете оновити "IE8 може робити лише потоки із встановленим плагіном Gears" до "IE8, а IE9 може робити лише теми з встановленим плагіном Gears"
BenoitParis

2
@ inf3rno для тривалих обчислень на іншому потоці, щоб вони не сповільнювали інтерфейс браузера.
Сем Хаслер

6
@SamHasler Ви можете переглянути свою відповідь. Зараз веб-працівників підтримують усі сучасні настільні браузери. Дивіться також caniuse.com/#search=worker
Rob W

2
@SamHasler також варто зазначити, що Google Gears більше не підтримується.
skeggse

73

Різний спосіб зробити багатопотокове та асинхронне в JavaScript

До HTML5 JavaScript дозволяв виконувати лише один потік на сторінці.

Був деякий Hacky спосіб імітації асинхронного виконання з Вихід , setTimeout(), setInterval(), XMLHttpRequestабо обробники подій (див в кінці цього поста для прикладу з виходом і setTimeout()).

Але за допомогою HTML5 ми тепер можемо використовувати Worker Threads для паралельного виконання функцій. Ось приклад використання.


Справжня мультиварка

Багатопотокові: Робочі нитки JavaScript

HTML5 представив нитки веб-робочих (див. Сумісність браузерів )
Примітка: IE9 та більш ранні версії не підтримують його.

Ці робочі потоки - це потоки JavaScript, які працюють у фоновому режимі, не впливаючи на продуктивність сторінки. Для отримання додаткової інформації про веб-працівника прочитайте документацію або цей підручник .

Ось простий приклад з 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>

Ми бачимо, що три потоки виконуються паралельно і друкують їх поточне значення на сторінці. Вони не заморожують сторінку, оскільки вони виконані у фоновому режимі з окремими потоками.


Багатопотоковість: з декількома рамками

Ще один спосіб досягти цього - використовувати кілька кадрів iframes , кожен з яких виконує потік. Ми можемо надати 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 з дохідністю

Вихід - це нова функція в ECMAScript 6 , вона працює лише в найстарішій версії Firefox та Chrome (у Chrome потрібно включити Експериментальний JavaScript, який відображається в хромі: // прапори / # enable-javascript-гармонія ).

Ключове слово урожай викликає призупинення виконання функції генератора, а значення виразу після ключового слова дохідності повертається на виклик генератора. Це можна розглядати як генератор версії ключового слова на основі генератора.

Генератор дозволяє призупинити виконання функції та відновити її пізніше. Генератор може бути використаний для планування ваших функцій за допомогою техніки, званої батут .

Ось приклад:

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
Це справді має бути найкращою відповіддю.
Джеррі Лю


11

Немає справжньої нитки в JavaScript. JavaScript, який є ковкою мовою, якою вона є, дозволяє вам емулювати деякі з них. Ось приклад, на який я натрапив днями.


1
Що ви маєте на увазі під "справжньою ниткою"? Зелені нитки - це справжні нитки.
Уес

10

У Javascript немає справжньої багатопотокової передачі, але ви можете отримати асинхронну поведінку за допомогою setTimeout()та асинхронних запитів AJAX.

Що саме ви намагаєтеся досягти?


7

Ось лише спосіб імітувати багатопотоковість у Javascript

Зараз я збираюся створити 3 потоки, які будуть обчислювати додавання чисел, числа можна розділити на 13, а числа можна розділити з 3 до 10000000000. І ці 3 функції не в змозі запускатись одночасно з тим, що означає Concurrency. Але я покажу вам трюк, який змусить ці функції виконуватись рекурсивно за один і той же час: 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, деякі з яких можна використовувати для підтримки потоків.


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

3

Якщо ви не можете або не хочете використовувати будь-які речі AJAX, використовуйте iframe або десять! ;) Ви можете мати процеси, що працюють у iframes паралельно з основною сторінкою, не турбуючись про проблеми, пов’язані з перехресним веб-переглядачем, або проблеми з синтаксисом з точковою мережею AJAX тощо. Ви можете викликати JavaScript головної сторінки (включаючи JavaScript, який вона імпортувала) з iframe

Наприклад, у батьківському iframe для виклику egFunction()в батьківському документі після завантаження вмісту iframe (це асинхронна частина)

parent.egFunction();

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


1
Цей опис був мені занадто коротким. Чи можете ви або детальніше розглянути, як зробити цю техніку, або опублікувати якесь посилання на підручник із деяким кодом?
олігофрен

3

Іншим можливим методом є використання інтерпретатора javascript у середовищі javascript.

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

Підхід дещо схожий з веб-працівниками, але ви даєте інтерпретатору доступ до глобального середовища браузера.

Я зробив невеликий проект, щоб продемонструвати це .

Більш детальне пояснення в цій публікації в блозі .


1

У JavaScript немає потоків, але у нас є працівники.

Робітники можуть бути хорошим вибором, якщо вам не потрібні спільні об’єкти.

Більшість реалізацій браузера фактично поширюють працівників по всіх ядрах, що дозволяє використовувати всі ядра. Демонстрацію цього ви можете побачити тут .

Я розробив бібліотеку під назвою task.js, що робить це дуже просто.

task.js Спрощений інтерфейс для отримання інтенсивного коду CPU для запуску на всіх ядрах (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+

Mozilla - 3,5+

Сафарі - 4.0+

Опера - 11,5+

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.