Czy JavaScript jest gwarantowany jako jednowątkowy?

610

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?

Egor Pavlikhin
źródło
25
Prawdopodobnie w kontekście przeglądarek. Ale niektóre programy pozwalają traktować JS jako język najwyższego poziomu i udostępniać powiązania dla innych bibliotek C ++. Na przykład flusspferd (wiązania C ++ dla JS - AWESOME BTW) robił pewne rzeczy z wielowątkowym JS. To zależy od kontekstu.
NG.

Odpowiedzi:

583

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 outwszystkich 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.onchangenasłuchiwanie i wpisz coś w danych wejściowych, zanim focus()wywołanie zostanie nieuzbrojone, a kolejność dziennika jest log in, change, blur, log outinna niż w Operze, gdzie jest, log in, blur, log out, changei IE, gdzie jest (nawet mniej zrozumiale) log in, change, log out, blur.

Podobnie wywołanie click()elementu, który go zapewnia, wywołuje onclickmoduł 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 addEventListeneri 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, alerta 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 showModalDialogw 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 setIntervalfunkcji. Jeśli piszesz platformę, której zamierzasz używać w złożonych aplikacjach, zrobienie tego może być dobrym posunięciem.postMessageMam nadzieję, że w przyszłości złagodzi ból związany ze skryptami między dokumentami.

Bobin
źródło
14
@JP: Osobiście nie chcę od razu wiedzieć, ponieważ oznacza to, że muszę uważać, aby mój kod został ponownie przyjęty, że wywołanie mojego kodu rozmycia nie wpłynie na stan, na którym opiera się jakiś kod zewnętrzny. Jest zbyt wiele przypadków, w których rozmycie jest nieoczekiwanym efektem ubocznym, aby koniecznie złapać każdego. I niestety, nawet jeśli tego chcesz, to nie jest niezawodne! IE uruchamia się, blur gdy kod zwróci kontrolę nad przeglądarką.
bobince
107
JavaScript jest jednowątkowy. Zatrzymanie wykonywania w alert () nie oznacza, że ​​wątek zdarzenia przestaje pompować zdarzenia. Oznacza to tylko, że skrypt śpi, gdy alert jest wyświetlany na ekranie, ale musi ciągle pompować zdarzenia, aby narysować ekran. Gdy alarm jest włączony, pompa zdarzeń działa, co oznacza, że ​​wysyłanie zdarzeń jest całkowicie poprawne. W najlepszym wypadku pokazuje to wspólne wątkowanie, które może się zdarzyć w javascript, ale wszystkie te zachowania można wyjaśnić funkcją po prostu dodając zdarzenie do pompy zdarzeń, która ma zostać przetworzona w późniejszym czasie, niż robiąc to teraz.
chubbsondubs,
34
Pamiętaj jednak, że gwintowanie kooperacyjne jest nadal jednowątkowe. Dwie rzeczy nie mogą się wydarzyć jednocześnie, na co pozwala wielowątkowość i wprowadza niedeterminizm. Wszystko, co zostało opisane, jest deterministyczne i stanowi dobre przypomnienie tego rodzaju problemów. Dobra robota w analizie @bobince
chubbsondubs
94
Chubbard ma rację: JavaScript jest jednowątkowy. To nie jest przykład wielowątkowości, ale raczej synchroniczne wysyłanie wiadomości w jednym wątku. Tak, można wstrzymać stos i kontynuować wysyłanie zdarzeń (np. Alert ()), ale rodzaje problemów z dostępem, które występują w prawdziwych środowiskach wielowątkowych, po prostu nie mogą się zdarzyć; na przykład, nigdy nie będziesz mieć zmiennej wartości zmieniającej się między testem a natychmiastowym późniejszym przypisaniem, ponieważ twojego wątku nie można arbitralnie przerwać. Obawiam się, że ta reakcja spowoduje jedynie zamieszanie.
Kris Giesing
19
Tak, ale biorąc pod uwagę, że funkcja blokująca, która czeka na dane wejściowe użytkownika, może nastąpić pomiędzy dowolnymi dwiema instrukcjami, potencjalnie masz wszystkie problemy z spójnością, jakie powodują wątki na poziomie systemu operacyjnego. To, czy silnik JavaScript działa w wielu wątkach systemu operacyjnego, nie ma większego znaczenia.
Bobin
115

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/setIntervalzdarzenia 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.)

Már Örlygsson
źródło
21
Zgadzam się. Jeśli wielowątkowość zostanie kiedykolwiek dodana do Javascript w przeglądarce, będzie to odbywało się za pośrednictwem jawnego interfejsu API (np. Web Workers), tak jak w przypadku wszystkich języków imperatywnych. To jedyny sposób, który ma sens.
Dean Harding
1
Zauważ, że jest jeden. główny wątek JS, ALE niektóre rzeczy działają równolegle w przeglądarce. To nie tylko wrażenie wielowątkowości. Żądania są faktycznie uruchamiane równolegle. Detektory zdefiniowane w JS są uruchamiane jeden po drugim, ale żądania są naprawdę równoległe.
Nux
16

Tak, chociaż nadal możesz cierpieć na niektóre problemy z jednoczesnym programowaniem (głównie warunki wyścigu) podczas korzystania z asynchronicznych interfejsów API, takich jak wywołania zwrotne setInterval i xmlhttp.

wydać
źródło
10

Tak, chociaż Internet Explorer 9 skompiluje JavaScript w osobnym wątku w ramach przygotowań do wykonania w głównym wątku. Nie zmienia to jednak niczego dla ciebie jako programisty.

ChessWhiz
źródło
8

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:

    1. Czy cały kod js jest wykonywany w jednym wątku ? : Tak
    1. Czy kod js może powodować uruchamianie innych wątków ? : Tak
    1. Czy te wątki mogą mutować kontekst wykonania js ?: Nie. Ale mogą (bezpośrednio / pośrednio (?)) Dołączyć do kolejki zdarzeń, z której detektory mogą mutować kontekst wykonania . Ale nie daj się zwieść, słuchacze znów biegną atomowo do głównego wątku .

Tak więc w przypadku przeglądarek i nodejs (i prawdopodobnie wielu innych silników) javascript nie jest wielowątkowy, ale same silniki są .


Aktualizacja o pracownikach sieci:

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:

O bezpieczeństwie wątków :

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.


PS

Oprócz teorii zawsze przygotuj się na możliwe przypadki narożne i błędy opisane w zaakceptowanej odpowiedzi

Marinos An
źródło
7

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ść. setTimeouti setIntervalsą 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ść).

Kok
źródło
7

W rzeczywistości okno nadrzędne może komunikować się z oknami lub ramkami potomnymi lub rodzeństwem, które mają uruchomione własne wątki wykonawcze.

Kennebec
źródło
6

@ Bobince zapewnia naprawdę nieprzejrzystą odpowiedź.

Odrzucając odpowiedź Marla Örlygssona, JavaScript jest zawsze jednowątkowy z tego prostego faktu: wszystko w Javascript jest wykonywane na jednej linii czasu.

Jest to ścisła definicja jednowątkowego języka programowania.

wle8300
źródło
4

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

max
źródło
2
Większość definicji języka jest jednak zaprojektowana tak, aby były efektywnie jednowątkowe i stwierdzają, że wielowątkowość jest dozwolona, ​​o ile efekt jest identyczny. (np. UML)
jevon,
1
Muszę się zgodzić z odpowiedzią po prostu dlatego, że obecny ECMAScript nie zawiera przepisu (chociaż wydaje mi się, że to samo można powiedzieć o C) w kontekście kontekstów wykonywania ECMAScript . Następnie, podobnie jak ta odpowiedź, argumentowałbym, że każda implementacja, która ma współbieżne wątki mogące modyfikować stan współdzielony, jest rozszerzeniem ECMAScript .
user2864740
3

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.

Francisco Soto
źródło
2

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.

Anton Alexandrenok
źródło
-4

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

James
źródło
5
chrome robi to we właściwy sposób, nie wiem, gdzie @James widzi, że jest to wielowątkowość ...: 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)
Tor Valamo