JavaScript jest znany jako jednowątkowy we wszystkich nowoczesnych implementacjach przeglądarki, ale czy jest to określone w jakimkolwiek standardzie, czy to tylko z tradycji? Czy można całkowicie założyć, że JavaScript jest zawsze jednowątkowy?
JavaScript jest znany jako jednowątkowy we wszystkich nowoczesnych implementacjach przeglądarki, ale czy jest to określone w jakimkolwiek standardzie, czy to tylko z tradycji? Czy można całkowicie założyć, że JavaScript jest zawsze jednowątkowy?
Odpowiedzi:
To dobre pytanie. Chciałbym powiedzieć „tak”. Nie mogę
Zazwyczaj uważa się, że JavaScript ma pojedynczy wątek wykonania widoczny dla skryptów (*), dzięki czemu po wprowadzeniu skryptu wbudowanego, detektora zdarzeń lub przekroczenia limitu czasu masz pełną kontrolę do momentu powrotu z końca bloku lub funkcji.
(*: ignorując pytanie, czy przeglądarki rzeczywiście implementują swoje silniki JS za pomocą jednego wątku systemu operacyjnego, czy też WebWorkers wprowadza inne ograniczone wątki wykonania).
Jednak w rzeczywistości nie jest to do końca prawdą , podstępnie i podstępnie.
Najczęstszym przypadkiem są zdarzenia natychmiastowe. Przeglądarki uruchomią je natychmiast, gdy Twój kod zrobi coś, co je spowoduje:
var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
l.value+= 'blur\n';
};
setTimeout(function() {
l.value+= 'log in\n';
l.focus();
l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">
Wyniki we log in, blur, log out
wszystkich oprócz IE. Te wydarzenia nie tylko odpalają, ponieważ dzwoniłeśfocus()
bezpośrednio, ale mogą się zdarzyć, ponieważ zadzwoniłeś alert()
lub otworzyłeś wyskakujące okienko lub cokolwiek innego, co porusza fokus.
Może to również skutkować innymi zdarzeniami. Na przykład dodaj i.onchange
nasłuchiwanie i wpisz coś w danych wejściowych, zanim focus()
wywołanie zostanie nieuzbrojone, a kolejność dziennika jest log in, change, blur, log out
inna niż w Operze, gdzie jest, log in, blur, log out, change
i IE, gdzie jest (nawet mniej zrozumiale) log in, change, log out, blur
.
Podobnie wywołanie click()
elementu, który go zapewnia, wywołuje onclick
moduł obsługi natychmiast we wszystkich przeglądarkach (przynajmniej jest to zgodne!).
(Korzystam on...
tutaj z właściwości bezpośredniego modułu obsługi zdarzeń, ale to samo dzieje się z addEventListener
i attachEvent
).
Istnieje również szereg okoliczności, w których zdarzenia mogą być uruchamiane, gdy Twój kod jest włączony, mimo że nie zrobiłeś nic, aby go sprowokować. Przykład:
var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
l.value+= 'alert in\n';
alert('alert!');
l.value+= 'alert out\n';
};
window.onresize= function() {
l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>
Hit, alert
a otrzymasz modalne okno dialogowe. Koniec wykonywania skryptu, dopóki nie odrzucisz tego dialogu, tak? Nie. Zmień rozmiar głównego okna, a otrzymaszalert in, resize, alert out
pole tekstowe.
Możesz myśleć, że zmiana rozmiaru okna jest niemożliwa, gdy modalne okno dialogowe jest uruchomione, ale nie jest tak: w Linuksie możesz zmieniać rozmiar okna tak, jak chcesz; w systemie Windows nie jest to takie łatwe, ale możesz to zrobić, zmieniając rozdzielczość ekranu z większej na mniejszą, w której okno nie pasuje, powodując zmianę rozmiaru.
Możesz pomyśleć, cóż, to tylko resize
(i prawdopodobnie kilka innych scroll
) może się uruchomić, gdy użytkownik nie ma aktywnej interakcji z przeglądarką, ponieważ skrypt jest wątkowy. A w przypadku pojedynczych okien możesz mieć rację. Ale wszystko to idzie w garść, gdy tylko zaczniesz pisać skrypty w różnych oknach. W przypadku wszystkich przeglądarek innych niż Safari, które blokują wszystkie okna / karty / ramki, gdy jedna z nich jest zajęta, możesz wchodzić w interakcje z dokumentem z kodu innego dokumentu, działając w osobnym wątku wykonania i powodując, że wszystkie powiązane zdarzenia obsługują zdarzenia ogień.
Miejsca, w których zdarzenia mogą być generowane, mogą być wywoływane, gdy skrypt jest wciąż podzielony na wątki:
gdy modalne pop-upy ( alert
, confirm
, prompt
) są otwarte, ale we wszystkich przeglądarkach Opera;
podczas showModalDialog
w przeglądarkach, które go obsługują;
okno dialogowe „Skrypt na tej stronie może być zajęty ...”, nawet jeśli zdecydujesz się na kontynuowanie działania skryptu, pozwala na uruchamianie zdarzeń takich jak zmiana rozmiaru i rozmycie, nawet gdy skrypt jest w środku zajęty, z wyjątkiem Opery.
jakiś czas temu dla mnie, w IE z wtyczką Sun Java, wywołanie dowolnej metody w aplecie może pozwolić na uruchomienie zdarzeń i ponowne wprowadzenie skryptu. To zawsze był błąd zależny od czasu i możliwe, że Sun go naprawił (mam nadzieję, że tak).
prawdopodobnie więcej. Minęło trochę czasu, odkąd to przetestowałem, a przeglądarki stały się bardziej złożone.
Podsumowując, większość użytkowników JavaScript wydaje się mieć pojedynczy wątek wykonania oparty na zdarzeniach. W rzeczywistości nie ma czegoś takiego. Nie jest jasne, ile z tego jest po prostu błędem, a ile przemyślanego projektu, ale jeśli piszesz złożone aplikacje, zwłaszcza takie, które wykorzystują skrypty okien / ramek, istnieje szansa, że cię ugryzie - i sporadycznie, trudne do debugowania sposoby.
Jeśli najgorsze dojdzie do najgorszego, możesz rozwiązać problemy z współbieżnością, pośrednicząc we wszystkich reakcjach na zdarzenia. Kiedy nadejdzie zdarzenie, upuść je w kolejce i załatw kolejkę w kolejności później, w setInterval
funkcji. Jeśli piszesz platformę, której zamierzasz używać w złożonych aplikacjach, zrobienie tego może być dobrym posunięciem.postMessage
Mam nadzieję, że w przyszłości złagodzi ból związany ze skryptami między dokumentami.
blur
gdy kod zwróci kontrolę nad przeglądarką.
Powiedziałbym tak - ponieważ praktycznie cały istniejący (przynajmniej cały nietrywialny) kod javascript zepsułby się, gdyby silnik javascript przeglądarki działał asynchronicznie.
Dodaj do tego fakt, że HTML5 już określa pracowników sieci (jawny, znormalizowany API dla wielowątkowego kodu javascript) wprowadzający wielowątkowość do podstawowego Javascript byłby w większości bezcelowy.
( Uwaga dla innych komentujących: chociaż setTimeout/setInterval
zdarzenia obciążenia żądania HTTP (XHR) i zdarzenia interfejsu użytkownika (kliknięcie, fokus itp.) Zapewniają ogólne wrażenie wielowątkowości - wszystkie są nadal wykonywane wzdłuż jednej osi czasu - jeden na czas - więc nawet jeśli nie znamy wcześniej ich kolejności wykonania, nie musisz się martwić zmianami warunków zewnętrznych podczas wykonywania procedury obsługi zdarzenia, funkcji czasowej lub wywołania zwrotnego XHR.)
Powiedziałbym, że specyfikacja nie uniemożliwia komuś stworzenia silnika, który uruchamia javascript w wielu wątkach , wymagając od kodu wykonania synchronizacji w celu uzyskania dostępu do stanu obiektu współużytkowanego.
Myślę, że jednowątkowy paradygmat nieblokujący wynikał z potrzeby uruchamiania javascript w przeglądarkach, w których interfejs użytkownika nigdy nie powinien blokować.
Nodejs podążył za podejściem przeglądarki .
Silnik Rhino obsługuje jednak uruchamianie kodu js w różnych wątkach . Wykonania nie mogą współdzielić kontekstu, ale mogą współdzielić zakres. W tym konkretnym przypadku dokumentacja stanowi:
... ”Rhino gwarantuje, że dostęp do właściwości obiektów JavaScript jest atomowy w wątkach, ale nie daje żadnych gwarancji dla skryptów wykonujących w tym samym zakresie w tym samym czasie. Jeśli dwa skrypty używają tego samego zakresu jednocześnie, skrypty są odpowiedzialny za koordynację dostępu do zmiennych współdzielonych . ”
Z lektury dokumentacji Rhino wywnioskowałem, że ktoś może napisać API javascript, które również spawnuje nowe wątki javascript, ale API byłoby specyficzne dla nosorożców (np. Węzeł może tylko spawnować nowy proces).
Wyobrażam sobie, że nawet w przypadku silnika obsługującego wiele wątków w javascript powinna istnieć kompatybilność ze skryptami, które nie uwzględniają wielowątkowości lub blokowania.
Ukrywanie przeglądarek i nodejs tak, jak widzę to:
Tak więc w przypadku przeglądarek i nodejs (i prawdopodobnie wielu innych silników) javascript nie jest wielowątkowy, ale same silniki są .
Obecność pracowników sieci uzasadnia ponadto, że javascript może być wielowątkowy, w tym sensie, że ktoś może utworzyć kod w javascript, który będzie działał w osobnym wątku.
Jednak: pracownicy sieci nie rozwiązują problemów tradycyjnych wątków, które mogą współdzielić kontekst wykonania. Nadal obowiązują powyższe reguły 2 i 3 , ale tym razem kod wątkowy jest tworzony przez użytkownika (program piszący js) w javascript.
Jedyną rzeczą do rozważenia jest liczba spawnowanych wątków z punktu widzenia wydajności (a nie współbieżności ). Patrz poniżej:
Interfejs roboczy tworzy rzeczywiste wątki na poziomie systemu operacyjnego, a programiści mogą obawiać się, że współbieżność może powodować „interesujące” efekty w kodzie, jeśli nie będziesz ostrożny.
Ponieważ jednak pracownicy sieci WWW dokładnie kontrolują punkty komunikacji z innymi wątkami, w rzeczywistości bardzo trudno jest powodować problemy z współbieżnością . Nie ma dostępu do komponentów nienależących do wątków ani do DOM. I musisz przekazywać określone dane do i z wątku przez szeregowane obiekty. Musisz więc naprawdę ciężko pracować, aby spowodować problemy w kodzie.
Oprócz teorii zawsze przygotuj się na możliwe przypadki narożne i błędy opisane w zaakceptowanej odpowiedzi
JavaScript / ECMAScript został zaprojektowany do pracy w środowisku hosta. Oznacza to, że JavaScript w rzeczywistości nic nie robi chyba że środowisko hosta zdecyduje się przeanalizować i wykonać dany skrypt oraz zapewnić obiekty środowiska, które pozwolą JavaScriptowi faktycznie się przydać (takie jak DOM w przeglądarkach).
Myślę, że dana funkcja lub blok skryptu będzie wykonywany wiersz po wierszu i jest to gwarantowane w JavaScript. Być może jednak środowisko hosta może wykonywać wiele skryptów jednocześnie. Lub środowisko hosta zawsze może zapewniać obiekt zapewniający wielowątkowość. setTimeout
i setInterval
są przykładami lub przynajmniej pseudo-przykładami środowiska hosta zapewniającymi sposób wykonywania współbieżności (nawet jeśli nie jest to dokładnie współbieżność).
Nie.
Idę tutaj przeciwko tłumowi, ale bądźcie ze mną. Pojedynczy skrypt JS ma być efektywnie jednowątkowy, ale to nie znaczy, że nie można go interpretować inaczej.
Powiedzmy, że masz następujący kod ...
var list = [];
for (var i = 0; i < 10000; i++) {
list[i] = i * i;
}
Jest to napisane z oczekiwaniem, że do końca pętli lista musi zawierać 10000 pozycji, które są kwadratem indeksu, ale maszyna wirtualna może zauważyć, że każda iteracja pętli nie wpływa na drugą, i ponownie zinterpretować za pomocą dwóch wątków.
Pierwszy wątek
for (var i = 0; i < 5000; i++) {
list[i] = i * i;
}
Drugi wątek
for (var i = 5000; i < 10000; i++) {
list[i] = i * i;
}
Upraszczam tutaj, ponieważ tablice JS są bardziej skomplikowane niż głupie fragmenty pamięci, ale jeśli te dwa skrypty są w stanie dodać wpisy do tablicy w sposób bezpieczny dla wątków, to zanim oba zostaną wykonane, będzie miała taki sam wynik jak wersja jednowątkowa.
Chociaż nie jestem świadomy, że jakakolwiek maszyna wirtualna wykrywa taki równoległy kod, wydaje się prawdopodobne, że mogłaby powstać w przyszłości dla maszyn wirtualnych JIT, ponieważ w niektórych sytuacjach mogłaby oferować większą prędkość.
Kontynuując tę koncepcję, możliwe jest, że kod zostanie opatrzony adnotacjami, aby maszyna wirtualna wiedziała, co przekonwertować na kod wielowątkowy.
// like "use strict" this enables certain features on compatible VMs.
"use parallel";
var list = [];
// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
list[i] = i * i;
}
Ponieważ pracownicy sieci przychodzą do Javascript, jest mało prawdopodobne, aby ten ... brzydszy system kiedykolwiek powstał, ale myślę, że bezpiecznie jest powiedzieć, że Javascript jest tradycją jednowątkową.
Cóż, Chrome jest wieloprocesowy i myślę, że każdy proces zajmuje się własnym kodem JavaScript, ale o ile kod wie, jest „jednowątkowy”.
W JavaScript nie ma żadnej obsługi wielowątkowości, przynajmniej nie jawnie, więc nie robi to różnicy.
Wypróbowałem przykład @ bobince z niewielkimi modyfikacjami:
<html>
<head>
<title>Test</title>
</head>
<body>
<textarea id="log" rows="20" cols="40"></textarea>
<br />
<button id="act">Run</button>
<script type="text/javascript">
let l= document.getElementById('log');
let b = document.getElementById('act');
let s = 0;
b.addEventListener('click', function() {
l.value += 'click begin\n';
s = 10;
let s2 = s;
alert('alert!');
s = s + s2;
l.value += 'click end\n';
l.value += `result = ${s}, should be ${s2 + s2}\n`;
l.value += '----------\n';
});
window.addEventListener('resize', function() {
if (s === 10) {
s = 5;
}
l.value+= 'resize\n';
});
</script>
</body>
</html>
Tak więc, kiedy naciśniesz Uruchom, zamknij okienko ostrzeżenia i wykonasz „pojedynczy wątek”, powinieneś zobaczyć coś takiego:
click begin
click end
result = 20, should be 20
Ale jeśli spróbujesz uruchomić to w stabilnej przeglądarce Opera lub Firefox w systemie Windows i zminimalizować / zmaksymalizować okno z wyskakującym okienkiem na ekranie, pojawi się coś takiego:
click begin
resize
click end
result = 15, should be 20
Nie chcę powiedzieć, że jest to „wielowątkowość”, ale jakiś fragment kodu został wykonany w niewłaściwym czasie, a ja nie spodziewałem się tego, a teraz mam uszkodzony stan. I lepiej wiedzieć o tym zachowaniu.
Spróbuj zagnieździć w sobie dwie funkcje setTimeout, które będą zachowywać się wielowątkowo (tzn. Zewnętrzny zegar nie będzie czekał na zakończenie wewnętrznego przed wykonaniem swojej funkcji).
setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)
(dla przypomnienia, twój ZAWSZE powinien ZAWSZE pojawiać się najpierw, a następnie logować się do konsoli)