Czy JavaScript może być jednowątkowy?

JavaScript jest znany jako jednowątkowy we wszystkich nowoczesnych implementacjach przeglądarki, ale czy jest to określone w jakimkolwiek standardzie, czy tylko przez tradycję? Czy jest całkowicie bezpieczne założenie, że JavaScript jest zawsze jednowątkowy?

Author: Michał Perłakowski, 2010-04-29

12 answers

Dobre pytanie. Chciałbym powiedzieć "tak". Nie mogę.

JavaScript jest zwykle uważany za posiadający pojedynczy wątek wykonania widoczny dla skryptów (*), tak więc po wpisaniu skryptu inline, detektora zdarzeń lub limitu czasu, pozostajesz całkowicie pod kontrolą aż do powrotu z końca bloku lub funkcji.

(*: ignorując pytanie, czy przeglądarki rzeczywiście implementują swoje silniki JS używając jednego wątku OS, czy też inne ograniczone wątki wykonania są wprowadzone przez Webworkerów.)

Jednak w rzeczywistości to nie jest do końca prawdą, w podstępny sposób.

Najczęstszym przypadkiem są zdarzenia natychmiastowe. Przeglądarki odpalą je od razu, gdy twój kod zrobi coś, aby je spowodować: {]}

<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">
<script type="text/javascript">
    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();
</script>

Wyniki w log in, blur, log out na wszystkich z wyjątkiem IE. Zdarzenia te nie wywołują się tylko dlatego, że wywołałeś focus() bezpośrednio, mogą się zdarzyć, ponieważ zadzwoniłeś alert(), lub otworzyłeś wyskakujące okno, lub cokolwiek innego, co porusza skup się.

Może to również prowadzić do innych zdarzeń. Na przykład dodaj słuchacz i.onchange i wpisz coś na wejściu przed wywołaniem focus(), a kolejność logów to log in, change, blur, log out, z wyjątkiem Opery, gdzie jest log in, blur, log out, change i IE, gdzie jest (jeszcze mniej wyjaśnione) log in, change, log out, blur.

Podobnie wywołanie click() na elemencie, który go dostarcza, natychmiast wywołuje obsługę onclick we wszystkich przeglądarkach (przynajmniej jest to zgodne!).

(używam bezpośrednich właściwości obsługi zdarzeń on... tutaj, ale to samo dzieje się z addEventListener i attachEvent.)

Jest też kilka okoliczności, w których zdarzenia mogą wystrzelić podczas gdy twój kod jest w wątku, mimo że nie zrobiłeś nic {33]}, aby go sprowokować. Przykład:

<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>
<script type="text/javascript">
    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';
    };
</script>

Naciśnij alert, a otrzymasz okno dialogowe. Żadnych skryptów, dopóki nie odrzucisz tego dialogu, tak? Nie. Zmień rozmiar głównego okna, a otrzymasz alert in, resize, alert out w textarea.

Możesz pomyśleć, że zmiana rozmiaru okna jest niemożliwa modalne okno dialogowe działa, ale nie tak: w Linuksie możesz zmieniać rozmiar okna tak, jak chcesz; w systemie Windows nie jest to takie proste, ale możesz to zrobić, zmieniając rozdzielczość ekranu z większego na mniejszy, gdzie okno nie pasuje, powodując jego zmianę rozmiaru.

Możesz pomyśleć, cóż, to tylko resize (i prawdopodobnie kilka innych podobnych scroll) może odpalić, gdy użytkownik nie ma aktywnej interakcji z przeglądarką, ponieważ skrypt jest wątkowy. A w przypadku pojedynczych okien możesz być racja. Ale to wszystko idzie na pot, jak tylko robisz Skrypty między oknami. W przypadku wszystkich przeglądarek innych niż Safari, które blokują wszystkie okna/karty/ramki, gdy któreś z nich jest zajęte, można wchodzić w interakcje z dokumentem z kodu innego dokumentu, uruchamiając go w osobnym wątku uruchamiania i powodując uruchomienie powiązanych procedur obsługi zdarzeń.

Miejsca, w których mogą być generowane zdarzenia, mogą być podnoszone podczas gdy skrypt jest nadal wątkowy:

  • Gdy modal popups (alert, confirm, prompt) są otwarte, we wszystkich przeglądarkach oprócz Opery;

  • Podczas showModalDialog na przeglądarkach, które go obsługują;

  • "skrypt na tej stronie może być zajęty..."okno dialogowe, nawet jeśli zdecydujesz się pozwolić skryptowi kontynuować działanie, pozwala zdarzenia takie jak zmiana rozmiaru i rozmycie uruchamiać i być obsługiwane nawet wtedy, gdy skrypt jest w środku pętli zajętości, z wyjątkiem Opery.

  • Jakiś czas temu dla mnie, w IE z wtyczką Sun Java, wywołanie dowolnej metody na aplet może pozwolić zdarzenia do odpalenia i skrypt, aby być ponownie wprowadzone. To zawsze był błąd wrażliwy na czas, i możliwe, że Słońce naprawiło go od tego czasu(mam nadzieję).

  • Prawdopodobnie więcej. Minęło trochę czasu, odkąd to przetestowałem i przeglądarki zyskały złożoność od tego czasu.

Podsumowując, JavaScript wydaje się dla większości użytkowników, przez większość czasu, mieć ścisły pojedynczy wątek uruchamiania oparty na zdarzeniach. W rzeczywistości nie ma czegoś takiego. Nie jest jasne jak wiele z tego jest po prostu błędem i jak bardzo celowym projektem, ale jeśli piszesz złożone aplikacje, szczególnie te, które obsługują Skrypty między oknami/ramkami, jest szansa, że cię ugryzie - i to w przerywany, trudny do debugowania sposób.

Jeśli najgorsze przyjdzie do najgorszego, możesz rozwiązać problemy współbieżności, kierując wszystkie odpowiedzi zdarzeń. Gdy pojawi się Zdarzenie, upuść je w kolejce i zajmij się kolejką w kolejności później, w funkcji setInterval. Jeśli piszesz framework, który zamierza być używany przez złożone aplikacje, robienie tego może być dobrym posunięciem. postMessage będzie również, miejmy nadzieję, złagodzić ból tworzenia skryptów krzyżowych w przyszłości.

 527
Author: bobince,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-05 22:51:30

Powiedziałbym tak - ponieważ praktycznie wszystkie istniejące (przynajmniej wszystkie nietrywialne) kod javascript pękłby, gdyby silnik javascript przeglądarki uruchamiał go asynchronicznie.

Dodaj do tego fakt, że HTML5 określa już pracowników sieci (jawne, ustandaryzowane API dla wielowątkowego kodu javascript) wprowadzenie wielowątkowego do podstawowego Javascript byłoby w większości bezcelowe.

(Uwaga dla innych komentujących: Mimo setTimeout/setInterval, HTTP-request onload events (XHR), oraz zdarzenia UI (click, focus, itp.) zapewniają prymitywne wrażenie wielowątkowości - wszystkie są wykonywane wzdłuż jednej osi czasu-po kolei - więc nawet jeśli nie znamy ich kolejności wykonania wcześniej, nie ma potrzeby martwić się o warunki zewnętrzne zmieniające się podczas wykonywania obsługi zdarzeń, funkcji timed lub wywołania zwrotnego XHR.)

 104
Author: Már Örlygsson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-29 00:51:18

Tak, chociaż nadal możesz cierpieć na niektóre problemy związane z programowaniem współbieżnym (głównie warunki rasowe), gdy używasz dowolnego asynchronicznego API, takiego jak setInterval i wywołania zwrotne xmlhttp.

 15
Author: spender,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-29 00:28:31

Tak, chociaż Internet Explorer 9 skompiluje Javascript na osobnym wątku w przygotowaniu do wykonania w głównym wątku. To jednak niczego nie zmienia dla Ciebie jako programisty.

 10
Author: ChessWhiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-29 00:30:41

JavaScript / ECMAScript jest zaprojektowany do życia w środowisku hosta. Oznacza to, że JavaScript w rzeczywistości nie robi nic , chyba że środowisko hosta zdecyduje się parsować i wykonać dany skrypt, i dostarczyć obiekty środowiska, które pozwalają JavaScript rzeczywiście być użyteczne (takie jak DOM w przeglądarkach).

Myślę, że dana funkcja lub blok skryptu będzie wykonywana linia po linii i to jest gwarantowane dla JavaScript. Jednak być może środowisko hosta mogłoby wykonać wiele skryptów w w tym samym czasie. Lub, środowisko hosta zawsze może dostarczyć obiekt, który zapewnia wielowątkowość. setTimeout i setInterval są przykładami, a przynajmniej pseudo-przykładami, środowiska hosta dostarczającego sposób wykonywania współbieżności (nawet jeśli nie jest to dokładnie współbieżność).

 7
Author: Bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-29 01:09:17

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

 7
Author: kennebec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-29 04:03:16

Powiedziałbym, że specyfikacja nie uniemożliwia komuś stworzenia silnika, który uruchamia javascript na wielu wątkach, wymagającego od kodu synchronizacji dostępu do stanu współdzielonego obiektu.

Myślę, że jednowątkowy paradygmat nieblokujący powstał z potrzeby uruchamiania javascript w przeglądarkach, w których interfejs użytkownika nigdy nie powinien się blokować.

Nodejs zastosował podejście przeglądarki.

Rhino Silnik jednak obsługuje uruchamianie kodu js w różnych wątki . Wykonania nie mogą współdzielić kontekstu, ale mogą współdzielić zakres. W tym konkretnym przypadku dokumentacja stwierdza:

..."Rhino gwarantuje, że dostęp do właściwości obiektów JavaScript jest atomowy w różnych wątkach, ale nie daje żadnych więcej 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ą odpowiedzialne za koordynowanie dostępu do współdzielonego zmienne ."

Z lektury dokumentacji Rhino wnioskuję, że może być możliwe, aby ktoś napisał javascript api, które również rodzi nowe wątki javascript, ale api byłoby specyficzne dla rhino (np. węzeł może tylko wywołać nowy proces).

Wyobrażam sobie, że nawet dla silnika, który obsługuje wiele wątków w javascript, powinna być kompatybilność ze skryptami, które nie uwzględniają wielowątkowości lub blokowania.

Przeglądanie przeglądarek i nodejs w sposób Widzę, że jest:

  • Czy cały kod js jest wykonywany w jednym wątku? : Tak.

  • Czy kod js może powodować uruchamianie innych wątków? : Tak.

  • Czy te wątki mogą mutować kontekst wykonania js?: Nie. Ale mogą (bezpośrednio / pośrednio (?)) dołącza do kolejki zdarzeń.

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

 5
Author: Marinos An,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-22 17:41:35

Nie.

/ Align = "left" / Pojedynczy skrypt JS ma być efektywnie z pojedynczym wątkiem, ale nie oznacza to, że nie można go inaczej interpretować. Załóżmy, ż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 na końcu pętli lista musi mieć 10000 pozycji, które są indeksem do kwadratu, ale maszyna wirtualna może zauważyć, że każda iteracja pętli nie wpływa na drugą, i reinterpretacja 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 kawałki pamięci, ale jeśli te dwa skrypty są w stanie dodać wpisy do tablicy w sposób bezpieczny dla wątku, to do czasu wykonania obu będzie miał taki sam wynik jak wersja jednowątkowa.

Chociaż nie jestem świadomy żadnej maszyny wirtualnej wykrywającej taki równoległy kod, wydaje się prawdopodobne, że może to nastąpić do istnienia w przyszłości dla maszyn wirtualnych JIT, ponieważ może zaoferować większą prędkość w niektórych sytuacjach.

Kontynuując tę koncepcję, możliwe jest, że kod może być adnotowany, aby dać maszynie wirtualnej znać, co konwertować 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, że to... brzydszy system kiedykolwiek powstanie, ale myślę, że można śmiało powiedzieć, że Javascript jest jednowątkowy według tradycji.

 4
Author: max,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-25 03:40:23

@ Bobince daje naprawdę nieprzezroczystą odpowiedź.

Odpowiadając na odpowiedź mára Örlygssona, Javascript jest zawsze jednowątkowy z powodu tego prostego faktu: wszystko w Javascript jest wykonywane wzdłuż jednej osi czasu.

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

 4
Author: williamle8300,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-25 22:22:38

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 żadnego wsparcia dla wielowątkowości, przynajmniej nie jawnie, więc to nie robi różnicy.

 2
Author: Francisco Soto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-29 00:40:48

Wypróbowałem przykład @ bobince z drobnymi 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>

Więc, po naciśnięciu Uruchom, Zamknij wyskakujące okienko alertu i zrób "pojedynczy wątek", powinieneś zobaczyć coś takiego:

click begin
click end
result = 20, should be 20

Ale jeśli spróbujesz uruchomić to w operze lub Firefoksie stabilnym na Windows i zminimalizować / zmaksymalizować okno z wyskakującym ostrzeżeniem na ekranie, to będzie coś takiego:

click begin
resize
click end
result = 15, should be 20

Nie chcę mówić, że to jest "wielowątkowość" , ale jakiś fragment kodu został przeze mnie wykonany w niewłaściwym czasie nie spodziewam się tego, a teraz mam zepsuty stan. I lepiej wiedzieć o tym zachowaniu.

 1
Author: SPYke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-26 12:13:25

Spróbuj zagnieżdżać dwie funkcje setTimeout wewnątrz siebie i będą one zachowywać się wielowątkowo(tzn. zewnętrzny timer nie będzie czekał na zakończenie wewnętrznej przed wykonaniem swojej funkcji).

 -4
Author: James,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-15 19:56:39