JavaScript i wątki


Odpowiedzi:


109

Zobacz http://caniuse.com/#search=worker, aby uzyskać najbardziej aktualne informacje pomocy technicznej.

Poniżej przedstawiono stan wsparcia około 2009 roku.


Słowa, które chcesz wyszukać w Google, to wątki robocze JavaScript

Oprócz Gears nie ma nic obecnie dostępnego, ale dużo się mówi o tym, jak to zaimplementować, więc przypuszczam, że obserwuję to pytanie, ponieważ odpowiedź bez wątpienia zmieni się w przyszłości.

Oto odpowiednia dokumentacja dla Gears: WorkerPool API

WHATWG ma projekt rekomendacji dla wątków roboczych: Pracownicy sieciowi

Jest też wątki robocze DOM w Mozilli


Aktualizacja: czerwiec 2009, obecny stan obsługi wątków JavaScript przez przeglądarki

Firefox 3.5 ma pracowników internetowych. Kilka demonstracji pracowników sieciowych, jeśli chcesz zobaczyć ich w akcji:

Wtyczkę Gears można również zainstalować w przeglądarce Firefox.

Safari 4 i nocne koszule WebKit mają wątki robocze :

Chrome ma wypaloną wtyczkę Gears, więc może wykonywać wątki, chociaż wymaga monitu o potwierdzenie od użytkownika (i używa innego interfejsu API dla pracowników internetowych, chociaż będzie działać w dowolnej przeglądarce z zainstalowaną wtyczką Gears):

  • Demo Google Gears WorkerPool (nie jest to dobry przykład, ponieważ działa zbyt szybko, aby przetestować w Chrome i Firefox, chociaż IE działa na tyle wolno, że blokuje interakcję)

IE8 i IE9 mogą wykonywać wątki tylko z zainstalowaną wtyczką Gears


1
Chociaż Safari 4 obsługuje pracowników sieci, wydaje się, że tylko Firefox obsługuje przekazywanie złożonych obiektów za pośrednictwem postMessage: hacks.mozilla.org/2009/07/working-smarter-not-harder Zobacz ostatni akapit tego postu na temat wykorzystania w świecie rzeczywistym w projekcie Bespin zawiera linki do podkładki, która implementuje interfejs Worker API pod kątem wtyczki Google Gears i dodaje brakujące funkcje do roboczej implementacji przeglądarki Safari 4 oraz szczegóły dotyczące sposobu implementacji przezroczystych zdarzeń niestandardowych w interfejsie postMessage.
Sam Hasler

6
Teraz IE9 jest już niedostępny, możesz zaktualizować „IE8 może wykonywać wątki tylko z zainstalowaną wtyczką Gears” do „IE8 i IE9 może wykonywać wątki tylko z zainstalowaną wtyczką Gears”
BenoitParis

2
@ inf3rno za wykonywanie długich obliczeń w innym wątku, aby nie spowalniać interfejsu przeglądarki.
Sam Hasler

6
@SamHasler Możesz zmienić swoją odpowiedź. Pracownicy sieciowi są teraz obsługiwani przez wszystkie nowoczesne przeglądarki komputerowe. Zobacz także caniuse.com/#search=worker
Rob W

2
@SamHasler warto również zauważyć, że Google Gears nie jest już obsługiwany.
skeggse

73

Inny sposób wykonywania wielowątkowości i asynchroniczności w JavaScript

Przed HTML5 JavaScript umożliwiał wykonanie tylko jednego wątku na stronę.

Było kilka hacky sposobem symulowania asynchronicznego wykonania z Wydajność , setTimeout(), setInterval(), XMLHttpRequestlub obsługi zdarzeń (patrz końca tego słupka na przykład z wydajnością i setTimeout()).

Ale dzięki HTML5 możemy teraz używać wątków roboczych do równoległego wykonywania funkcji. Oto przykład użycia.


Prawdziwa wielowątkowość

Wielowątkowość: wątki robocze JavaScript

HTML5 wprowadził wątki Web Worker (patrz: kompatybilność przeglądarek )
Uwaga: IE9 i wcześniejsze wersje go nie obsługują.

Te wątki robocze to wątki JavaScript, które działają w tle bez wpływu na wydajność strony. Aby uzyskać więcej informacji o programie Web Worker, przeczytaj dokumentację lub ten samouczek .

Oto prosty przykład z 3 wątkami Web Worker, które liczą się do MAX_VALUE i pokazują bieżącą obliczoną wartość na naszej stronie:

//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>

Widzimy, że trzy wątki są wykonywane współbieżnie i wypisują ich aktualną wartość na stronie. Nie zamrażają strony, ponieważ są wykonywane w tle z oddzielnymi wątkami.


Wielowątkowość: z wieloma ramkami iframe

Innym sposobem osiągnięcia tego jest użycie wielu ramek iframe , z których każdy wykona wątek. Możemy podać elementowi iframe kilka parametrów poprzez adres URL, a element iframe może komunikować się z jego rodzicem w celu uzyskania wyniku i wydrukowania go z powrotem (element iframe musi znajdować się w tej samej domenie).

Ten przykład nie działa we wszystkich przeglądarkach! iframe zwykle działają w tym samym wątku / procesie co strona główna (ale Firefox i Chromium wydają się traktować to inaczej).

Ponieważ fragment kodu nie obsługuje wielu plików HTML, podam tutaj tylko różne kody:

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);
    }
})();

Symuluj wielowątkowość

Jednowątkowy: emuluj współbieżność JavaScript z setTimeout ()

Naiwnym sposobem byłoby wykonywanie funkcji setTimeout()jedna po drugiej w ten sposób:

setTimeout(function(){ /* Some tasks */ }, 0);
setTimeout(function(){ /* Some tasks */ }, 0);
[...]

Ale ta metoda nie działa, ponieważ każde zadanie będzie wykonywane jedno po drugim.

Możemy zasymulować wykonanie asynchroniczne, wywołując funkcję rekurencyjnie w następujący sposób:

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>

Jak widać ta druga metoda jest bardzo powolna i zawiesza przeglądarkę, ponieważ wykorzystuje główny wątek do wykonywania funkcji.


Jednowątkowy: emuluj współbieżność JavaScript z wydajnością

Yield to nowa funkcja w ECMAScript 6 , działa tylko na najstarszej wersji Firefoksa i Chrome (w Chrome musisz włączyć Eksperymentalny JavaScript pojawiający się w chrome: // flags / # enable-javascript-harmony ).

Słowo kluczowe yield powoduje wstrzymanie wykonywania funkcji generatora, a wartość wyrażenia następującego po słowie kluczowym yield jest zwracana do obiektu wywołującego generator. Można to traktować jako wersję słowa kluczowego return opartą na generatorze.

Generator umożliwia wstrzymanie wykonywania funkcji i wznowienie jej później. Generator może służyć do planowania funkcji za pomocą techniki zwanej trampolinem .

Oto przykład:

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
To naprawdę powinna być najlepsza odpowiedź.
Jerry Liu,

14

Dzięki HTML5 „side-specs” nie ma już potrzeby hakowania javascript za pomocą setTimeout (), setInterval () itp.

HTML5 & Friends wprowadza specyfikację javascript Web Workers . Jest to interfejs API do asynchronicznego i niezależnego uruchamiania skryptów.

Linki do specyfikacji i samouczka .


11

W JavaScript nie ma prawdziwego wątku. JavaScript, który jest plastycznym językiem, pozwala na jego emulację. Oto przykład , na który natknąłem się pewnego dnia.


1
Co masz na myśli mówiąc o „prawdziwym wątkowaniu”? Zielone nici to prawdziwe wątki.
Wes,

10

W JavaScript nie ma prawdziwej wielowątkowości, ale możesz uzyskać asynchroniczne zachowanie za pomocą setTimeout()i asynchronicznych żądań AJAX.

Co dokładnie próbujesz osiągnąć?


7

Oto tylko sposób na symulację wielowątkowości w JavaScript

Teraz utworzę 3 wątki, które będą obliczać dodawanie liczb, liczby można podzielić przez 13, a liczby przez 3 do 10000000000. A te 3 funkcje nie są w stanie działać w tym samym czasie, co oznacza Współbieżność. Ale pokażę ci sztuczkę, która sprawi, że te funkcje będą działać rekurencyjnie w tym samym czasie: jsFiddle

Ten kod należy do mnie.

Część ciała

    <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>

Część 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();

Mam nadzieję, że ten sposób będzie pomocny.


6

Możesz użyć Narrative JavaScript , kompilatora, który przekształci twój kod w maszynę stanów, skutecznie umożliwiając emulację wątków. Czyni to poprzez dodanie do języka operatora „yielding” (zapisanego jako „->”), który umożliwia pisanie kodu asynchronicznego w pojedynczym, liniowym bloku kodu.



3

W surowym JavaScript najlepsze, co możesz zrobić, to użyć kilku wywołań asynchronicznych (xmlhttprequest), ale nie jest to tak naprawdę wielowątkowe i bardzo ograniczone. Google Gears dodaje do przeglądarki szereg interfejsów API, z których niektóre mogą być używane do obsługi wątków.


1
Interfejs API Google Gears nie jest już dostępny.
Ludovic Feltz

3

Jeśli nie możesz lub nie chcesz używać żadnych elementów AJAX, użyj elementu iframe lub dziesięciu! ;) Możesz mieć procesy działające w ramkach iframe równolegle ze stroną wzorcową bez martwienia się o problemy porównywalne z różnymi przeglądarkami lub problemy ze składnią z dot net AJAX itp., A także możesz wywołać JavaScript strony wzorcowej (w tym JavaScript, który został zaimportowany) z pliku iframe.

Np. W nadrzędnym elemencie iframe, aby wywołać egFunction()dokument nadrzędny po załadowaniu treści iframe (to jest część asynchroniczna)

parent.egFunction();

Dynamicznie generuj ramki iframe, aby główny kod HTML był od nich wolny, jeśli chcesz.


1
Ten opis był trochę za krótki jak na mój gust. Czy możesz rozwinąć tę technikę lub zamieścić link do samouczka pokazującego kod?
oligofren

3

Inną możliwą metodą jest użycie interpretera javascript w środowisku javascript.

Tworząc wielu interpreterów i kontrolując ich wykonanie z poziomu głównego wątku, można symulować wielowątkowość z każdym wątkiem działającym w swoim własnym środowisku.

Podejście jest nieco podobne do pracowników sieci, ale dajesz tłumaczowi dostęp do globalnego środowiska przeglądarki.

Zrobiłem mały projekt, aby to zademonstrować .

Bardziej szczegółowe wyjaśnienie w tym poście na blogu .


1

Javascript nie ma wątków, ale mamy pracowników.

Pracownicy mogą być dobrym wyborem, jeśli nie potrzebujesz udostępnionych obiektów.

Większość implementacji przeglądarek w rzeczywistości rozdziela procesy robocze na wszystkie rdzenie, umożliwiając wykorzystanie wszystkich rdzeni. Możesz zobaczyć demo tego tutaj .

Opracowałem bibliotekę o nazwie task.js, która bardzo to ułatwia.

task.js Uproszczony interfejs do pobierania kodu obciążającego procesor do uruchamiania na wszystkich rdzeniach (node.js i web)

Przykładem może być

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

Dzięki specyfikacji HTML5 nie musisz pisać zbyt dużo JS dla tego samego ani szukać hacków.

Jedną z funkcji wprowadzonych w HTML5 są Web Workers czyli JavaScript działający w tle, niezależnie od innych skryptów, bez wpływu na wydajność strony.

Jest obsługiwany w prawie wszystkich przeglądarkach:

Chrome - 4.0+

IE - 10.0+

Mozilla - 3.5+

Safari - 4.0+

Opera - 11.5+

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.