Ist JavaScript garantiert Single-Threaded?

610

Es ist bekannt, dass JavaScript in allen modernen Browser-Implementierungen Single-Threaded ist. Ist dies jedoch in einem Standard festgelegt oder nur aus Tradition? Ist es absolut sicher anzunehmen, dass JavaScript immer Single-Threaded ist?

Egor Pavlikhin
quelle
25
Im Kontext von Browsern wahrscheinlich. In einigen Programmen können Sie JS jedoch als Sprache der obersten Ebene behandeln und Bindungen für andere C ++ - Bibliotheken bereitstellen. Zum Beispiel hat flusspferd (C ++ - Bindungen für JS - AWESOME BTW) einige Sachen mit Multithread-JS gemacht. Es liegt am Kontext.
NG.
11
Dies ist ein Muss zu lesen: developer.mozilla.org/en/docs/Web/JavaScript/EventLoop
RickyA

Antworten:

583

Das ist eine gute Frage. Ich würde gerne "Ja" sagen. Ich kann nicht

Bei JavaScript wird normalerweise davon ausgegangen, dass ein einzelner Ausführungsthread für Skripte sichtbar ist (*), sodass Sie bei der Eingabe Ihres Inline-Skripts, Ereignis-Listeners oder Timeouts die vollständige Kontrolle behalten, bis Sie vom Ende Ihres Blocks oder Ihrer Funktion zurückkehren.

(*: Ignoriert die Frage, ob Browser ihre JS-Engines wirklich mit einem Betriebssystem-Thread implementieren oder ob andere eingeschränkte Ausführungsthreads von WebWorkern eingeführt werden.)

In Wirklichkeit ist dies jedoch nicht ganz richtig , auf hinterhältige Weise.

Der häufigste Fall sind unmittelbare Ereignisse. Browser werden diese sofort auslösen, wenn Ihr Code etwas unternimmt, um sie zu verursachen:

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

Ergebnisse in log in, blur, log outallen außer IE. Diese Ereignisse werden nicht nur ausgelöst, weil Sie focus()direkt angerufen haben. Sie können auch auftreten, weil Sie angerufen alert()oder ein Popup-Fenster geöffnet haben oder etwas anderes, das den Fokus bewegt.

Dies kann auch zu anderen Ereignissen führen. Fügen Sie beispielsweise einen i.onchangeListener hinzu und geben Sie etwas in die Eingabe ein, bevor der focus()Aufruf die Fokussierung aufhebt, und die Protokollreihenfolge ist log in, change, blur, log out, außer in Opera, wo es ist, log in, blur, log out, changeund IE, wo es ist (noch weniger erklärbar) log in, change, log out, blur.

In ähnlicher Weise ruft das Aufrufen click()eines Elements, das es bereitstellt, den onclickHandler sofort in allen Browsern auf (zumindest ist dies konsistent!).

(Ich verwende hier die on...Eigenschaften des direkten Ereignishandlers, aber das gleiche passiert mit addEventListenerund attachEvent.)

Es gibt auch eine Reihe von Umständen, unter denen Ereignisse ausgelöst werden können, während Ihr Code eingefügt wird, obwohl Sie nichts unternommen haben , um ihn zu provozieren. Ein Beispiel:

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>

Drücken alertSie und Sie erhalten ein modales Dialogfeld. Es wird kein Skript mehr ausgeführt, bis Sie diesen Dialog schließen, ja? Nee. Ändern Sie die Größe des Hauptfensters und Sie gelangen alert in, resize, alert outin den Textbereich.

Sie könnten denken, dass es unmöglich ist, die Größe eines Fensters zu ändern, während ein modales Dialogfeld geöffnet ist, aber nicht so: Unter Linux können Sie die Größe des Fensters beliebig ändern. Unter Windows ist das nicht so einfach, aber Sie können dies tun, indem Sie die Bildschirmauflösung von einer größeren auf eine kleinere ändern, wenn das Fenster nicht passt, wodurch die Größe geändert wird.

Sie könnten denken, es ist nur resize(und wahrscheinlich noch ein paar mehr scroll), die ausgelöst werden können, wenn der Benutzer keine aktive Interaktion mit dem Browser hat, weil das Skript mit einem Thread versehen ist. Und für einzelne Fenster könnten Sie Recht haben. Aber das alles geht in den Topf, sobald Sie Cross-Window-Scripting machen. Für alle Browser außer Safari, die alle Fenster / Registerkarten / Frames blockieren, wenn einer von ihnen beschäftigt ist, können Sie mit einem Dokument aus dem Code eines anderen Dokuments interagieren, in einem separaten Ausführungsthread ausgeführt werden und alle zugehörigen Ereignishandler dazu veranlassen Feuer.

Orte, an denen Ereignisse, die generiert werden können, ausgelöst werden können, während das Skript noch in einem Thread ausgeführt wird:

  • wenn die modalen Popups ( alert, confirm, prompt) offen sind, in allen Browsern , aber Opera;

  • während showModalDialogauf Browsern, die es unterstützen;

  • Das Dialogfeld "Ein Skript auf dieser Seite ist möglicherweise ausgelastet ..." ermöglicht, dass Ereignisse wie Größenänderung und Unschärfe ausgelöst und behandelt werden, auch wenn sich das Skript in der Mitte von a befindet Busy-Loop, außer in Opera.

  • Vor einiger Zeit konnte ich im IE mit dem Sun Java Plugin durch Aufrufen einer beliebigen Methode in einem Applet Ereignisse auslösen und Skripte erneut eingeben. Dies war immer ein zeitkritischer Fehler, und es ist möglich, dass Sun ihn seitdem behoben hat (ich hoffe es sehr).

  • wahrscheinlich mehr. Es ist eine Weile her, seit ich dies getestet habe und Browser haben seitdem an Komplexität gewonnen.

Zusammenfassend lässt sich sagen, dass JavaScript für die meisten Benutzer meistens einen strengen ereignisgesteuerten einzelnen Ausführungsthread aufweist. In Wirklichkeit hat es so etwas nicht. Es ist nicht klar, wie viel davon einfach ein Fehler ist und wie viel absichtliches Design, aber wenn Sie komplexe Anwendungen schreiben, insbesondere fensterübergreifende / Frame-Scripting-Anwendungen, besteht jede Möglichkeit, dass sie Sie beißen - und zwar zeitweise. schwer zu debuggende Möglichkeiten.

Wenn das Schlimmste zum Schlimmsten kommt, können Sie Parallelitätsprobleme lösen, indem Sie alle Ereignisantworten indirekt ausführen. Wenn ein Ereignis eintritt, legen Sie es in einer Warteschlange ab und behandeln Sie die Warteschlange in der Reihenfolge später in einer setIntervalFunktion. Wenn Sie ein Framework schreiben, das von komplexen Anwendungen verwendet werden soll, ist dies möglicherweise ein guter Schritt. postMessagewird hoffentlich auch den Schmerz des Cross-Document-Scripting in Zukunft lindern.

Bobince
quelle
14
@JP: Ich persönlich möchte es nicht sofort wissen, da dies bedeutet, dass ich darauf achten muss, dass mein Code erneut eingegeben wird und dass das Aufrufen meines Unschärfecodes keinen Einfluss auf den Status hat, auf den sich ein äußerer Code stützt. Es gibt zu viele Fälle, in denen Unschärfe eine unerwartete Nebenwirkung ist, um notwendigerweise jeden zu erfassen. Und leider ist es nicht zuverlässig, auch wenn Sie es wollen! Der IE wird ausgelöst, blur nachdem Ihr Code die Kontrolle an den Browser zurückgegeben hat.
Bobince
107
Javascript ist Single-Threaded. Das Anhalten Ihrer Ausführung bei alert () bedeutet nicht, dass der Ereignisthread keine Ereignisse mehr pumpt. Bedeutet nur, dass Ihr Skript im Ruhezustand ist, während die Warnung auf dem Bildschirm angezeigt wird. Es muss jedoch weiterhin Ereignisse pumpen, um den Bildschirm zu zeichnen. Während eine Warnung aktiv ist, läuft die Ereignispumpe, was bedeutet, dass es völlig korrekt ist, weiterhin Ereignisse zu senden. Bestenfalls zeigt dies ein kooperatives Threading, das in Javascript auftreten kann, aber all dieses Verhalten könnte durch eine Funktion erklärt werden, die einfach ein Ereignis an die Ereignispumpe anhängt, um es zu einem späteren Zeitpunkt zu verarbeiten, anstatt es jetzt zu tun.
Chubbsondubs
34
Denken Sie jedoch daran, dass kooperatives Threading immer noch Single-Threading ist. Zwei Dinge können nicht gleichzeitig passieren, was Multithreading erlaubt und Nicht-Determinismus injiziert. Alles, was beschrieben wurde, ist deterministisch und eine gute Erinnerung an diese Art von Problemen. Gute Arbeit bei der Analyse @bobince
chubbsondubs
94
Chubbard hat recht: JavaScript ist Single-Threaded. Dies ist kein Beispiel für Multithreading, sondern für den synchronen Nachrichtenversand in einem einzelnen Thread. Ja, es ist möglich, den Stapel anzuhalten und den Ereignisversand fortzusetzen (z. B. alert ()), aber die Arten von Zugriffsproblemen, die in echten Multithread-Umgebungen auftreten, können einfach nicht auftreten. Beispielsweise haben Sie zwischen einem Test und einer unmittelbar nachfolgenden Zuweisung niemals variable Änderungswerte, da Ihr Thread nicht willkürlich unterbrochen werden kann. Ich befürchte, dass diese Antwort nur Verwirrung stiften wird.
Kris Giesing
19
Ja, aber da eine Blockierungsfunktion, die auf Benutzereingaben wartet, zwischen zwei beliebigen Anweisungen auftreten kann, treten möglicherweise alle Konsistenzprobleme auf, die Threads auf Betriebssystemebene verursachen. Ob die JavaScript-Engine tatsächlich in mehreren Betriebssystem-Threads ausgeführt wird, ist von geringer Bedeutung.
Bobince
115

Ich würde ja sagen - weil praktisch der gesamte vorhandene (zumindest nicht triviale) Javascript-Code kaputt gehen würde, wenn die Javascript-Engine eines Browsers ihn asynchron ausführen würde.

Hinzu kommt, dass die Tatsache, dass HTML5 bereits Web Worker spezifiziert (eine explizite, standardisierte API für Multithreading-Javascript-Code), Multithreading in das grundlegende Javascript einführt, größtenteils sinnlos wäre.

( Hinweis für andere Kommentatoren: Auch wenn setTimeout/setIntervalHTTP-Request-Onload-Ereignisse (XHR) und UI-Ereignisse (Klicken, Fokussieren usw.) einen groben Eindruck von Multithreading vermitteln - sie werden immer noch alle auf einer einzigen Zeitachse ausgeführt - eins nach dem anderen eine Zeit - auch wenn wir ihre Ausführungsreihenfolge nicht im Voraus kennen, besteht kein Grund zur Sorge, dass sich die externen Bedingungen während der Ausführung eines Ereignishandlers, einer zeitgesteuerten Funktion oder eines XHR-Rückrufs ändern.)

Már Örlygsson
quelle
21
Genau. Wenn im Browser jemals Multithreading zu Javascript hinzugefügt wird, erfolgt dies über eine explizite API (z. B. Web Worker), genau wie bei allen wichtigen Sprachen. Nur so macht es Sinn.
Dean Harding
1
Beachten Sie, dass es eine einzige gibt. Haupt-JS-Thread, ABER einige Dinge werden parallel im Browser ausgeführt. Es ist nicht nur ein Eindruck von Multithreading. Anfragen werden tatsächlich parallel ausgeführt. Die Listener, die Sie in JS definieren, werden einzeln ausgeführt, aber die Anforderungen sind wirklich parallel.
Nux
16

Ja, obwohl bei der Verwendung einer der asynchronen APIs wie setInterval- und xmlhttp-Rückrufen immer noch Probleme mit der gleichzeitigen Programmierung auftreten können (hauptsächlich unter Rennbedingungen).

Spender
quelle
10

Ja, obwohl Internet Explorer 9 Ihr Javascript in einem separaten Thread kompiliert, um die Ausführung im Hauptthread vorzubereiten. Für Sie als Programmierer ändert dies jedoch nichts.

ChessWhiz
quelle
8

Ich würde sagen , dass die Spezifikation nicht hindert jemand von einem Motor zu schaffen , dass läuft auf JavaScript mehr Threads , den Code erfordert Synchronisierung durchzuführen für den Zugriff auf gemeinsam genutzte Objekt Zustand.

Ich denke, das Single-Threaded -Paradigma ohne Blockierung entstand aus der Notwendigkeit, Javascript in Browsern auszuführen, in denen die Benutzeroberfläche niemals blockieren sollte.

Nodejs ist dem Ansatz der Browser gefolgt .

Die Rhino- Engine unterstützt jedoch das Ausführen von JS-Code in verschiedenen Threads . Die Ausführungen können den Kontext nicht gemeinsam nutzen, sie können jedoch den Umfang gemeinsam nutzen. Für diesen speziellen Fall heißt es in der Dokumentation:

... "Rhino garantiert, dass der Zugriff auf Eigenschaften von JavaScript-Objekten über Threads hinweg atomar ist, gibt jedoch keine weiteren Garantien für Skripte, die gleichzeitig im selben Bereich ausgeführt werden. Wenn zwei Skripte gleichzeitig denselben Bereich verwenden, sind dies die Skripte verantwortlich für die Koordination aller Zugriffe auf gemeinsam genutzte Variablen . "

Aus dem Lesen der Rhino-Dokumentation schließe ich, dass es möglich sein kann, dass jemand eine Javascript-API schreibt, die auch neue Javascript-Threads erzeugt, aber die API wäre rhino-spezifisch (z. B. kann der Knoten nur einen neuen Prozess erzeugen).

Ich stelle mir vor, dass selbst für eine Engine, die mehrere Threads in Javascript unterstützt, Kompatibilität mit Skripten bestehen sollte, die Multithreading oder Blockierung nicht berücksichtigen.

Concearning Browser und NodeJS die Art und Weise sehe ich es:

    1. Wird der gesamte js- Code in einem einzigen Thread ausgeführt ? : Ja.
    1. Kann js Code dazu führen, dass andere Threads ausgeführt werden ? : Ja.
    1. Können diese Threads den Ausführungskontext mutieren ?: Nein. Sie können jedoch (direkt / indirekt (?)) An die Ereigniswarteschlange anhängen, aus der Listener den Ausführungskontext mutieren können . Aber lassen Sie sich nicht täuschen, die Listener laufen wieder atomar auf dem Haupt-Thread .

Im Fall von Browsern und Nodejs (und wahrscheinlich vielen anderen Engines) ist Javascript kein Multithread- Programm, sondern die Engines selbst .


Update zu Web-Workern:

Die Anwesenheit von Web-Workern rechtfertigt ferner, dass Javascript Multithread-fähig sein kann, in dem Sinne, dass jemand Code in Javascript erstellen kann, der auf einem separaten Thread ausgeführt wird.

Allerdings: Web-Worker analysieren nicht die Probleme herkömmlicher Threads , die den Ausführungskontext gemeinsam nutzen können. Die obigen Regeln 2 und 3 gelten weiterhin , aber diesmal wird der Thread-Code vom Benutzer (js code writer) in Javascript erstellt.

Das einzige, was zu berücksichtigen ist, ist die Anzahl der erzeugten Threads unter dem Gesichtspunkt der Effizienz (und nicht der Parallelität ). Siehe unten:

Informationen zur Thread-Sicherheit :

Die Worker-Oberfläche erzeugt echte Threads auf Betriebssystemebene, und aufmerksame Programmierer sind möglicherweise besorgt, dass Parallelität „interessante“ Effekte in Ihrem Code verursachen kann, wenn Sie nicht vorsichtig sind.

Da Web-Worker die Kommunikationspunkte mit anderen Threads sorgfältig kontrolliert haben , ist es tatsächlich sehr schwierig, Parallelitätsprobleme zu verursachen . Es gibt keinen Zugriff auf nicht threadsichere Komponenten oder das DOM. Und Sie müssen bestimmte Daten in und aus einem Thread durch serialisierte Objekte übergeben. Sie müssen also wirklich hart arbeiten, um Probleme in Ihrem Code zu verursachen.


PS

Seien Sie neben der Theorie immer auf mögliche Eckfälle und Fehler vorbereitet, die in der akzeptierten Antwort beschrieben sind

Marinos An
quelle
7

JavaScript / ECMAScript wurde entwickelt, um in einer Host-Umgebung zu leben. Das heißt, JavaScript führt tatsächlich nichts aus, es sei denn, die Host-Umgebung beschließt, ein bestimmtes Skript zu analysieren und auszuführen und Umgebungsobjekte bereitzustellen, mit denen JavaScript tatsächlich nützlich ist (z. B. das DOM in Browsern).

Ich denke, eine bestimmte Funktion oder ein Skriptblock wird zeilenweise ausgeführt, und das ist für JavaScript garantiert. Möglicherweise kann eine Host-Umgebung jedoch mehrere Skripts gleichzeitig ausführen. Oder eine Host-Umgebung kann immer ein Objekt bereitstellen, das Multithreading bereitstellt. setTimeoutund setIntervalsind Beispiele oder zumindest Pseudobeispiele einer Host-Umgebung, die eine Möglichkeit bietet, eine gewisse Parallelität durchzuführen (auch wenn es sich nicht genau um eine Parallelität handelt).

Bob
quelle
7

Tatsächlich kann ein übergeordnetes Fenster mit untergeordneten oder Geschwisterfenstern oder Frames kommunizieren, auf denen eigene Ausführungsthreads ausgeführt werden.

kennebec
quelle
6

@ Bobince liefert eine wirklich undurchsichtige Antwort.

In Anlehnung an Már Örlygssons Antwort ist Javascript aufgrund dieser einfachen Tatsache immer ein Thread: Alles in Javascript wird entlang einer einzigen Zeitachse ausgeführt.

Das ist die strikte Definition einer Single-Threaded-Programmiersprache.

wle8300
quelle
4

Nein.

Ich gehe hier gegen die Menge, aber ertrage es mit mir. Ein einzelnes JS-Skript soll effektiv Single-Threaded sein, dies bedeutet jedoch nicht, dass es nicht anders interpretiert werden kann.

Angenommen, Sie haben den folgenden Code ...

var list = [];
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Dies wird mit der Erwartung geschrieben, dass die Liste am Ende der Schleife 10000 Einträge enthalten muss, die den Index im Quadrat darstellen. Die VM kann jedoch feststellen, dass jede Iteration der Schleife keine Auswirkungen auf die andere hat, und mithilfe von zwei Threads neu interpretieren.

Erster Thread

for (var i = 0; i < 5000; i++) {
  list[i] = i * i;
}

Zweiter Thread

for (var i = 5000; i < 10000; i++) {
  list[i] = i * i;
}

Ich vereinfache dies hier, weil JS-Arrays komplizierter sind als dumme Speicherblöcke. Wenn diese beiden Skripte jedoch in der Lage sind, Einträge auf threadsichere Weise zum Array hinzuzufügen, ist dies zu dem Zeitpunkt der Ausführung der beiden Skripte der Fall das gleiche Ergebnis wie die Single-Threaded-Version.

Obwohl mir keine VM bekannt ist, die parallelisierbaren Code wie diesen erkennt, scheint es wahrscheinlich, dass er in Zukunft für JIT-VMs ​​entstehen könnte, da er in einigen Situationen mehr Geschwindigkeit bieten könnte.

Wenn Sie dieses Konzept weiterentwickeln, ist es möglich, dass Code mit Anmerkungen versehen wird, damit die VM weiß, was in Multithread-Code konvertiert werden soll.

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

Da Web Worker zu Javascript kommen, ist es unwahrscheinlich, dass dieses ... hässlichere System jemals ins Leben gerufen wird, aber ich denke, man kann mit Sicherheit sagen, dass Javascript traditionell aus einem einzigen Thread besteht.

max
quelle
2
Die meisten Sprachdefinitionen sind jedoch so konzipiert, dass sie effektiv Single-Threading sind, und geben an, dass Multithreading zulässig ist, solange der Effekt identisch ist. (zB UML)
Jevon
1
Ich muss der Antwort einfach zustimmen, weil das aktuelle ECMAScript keine Vorkehrungen für gleichzeitige ECMAScript-Ausführungskontexte trifft (obwohl ich denke, dass dies auch für C gilt) . Dann würde ich wie diese Antwort argumentieren, dass jede Implementierung, bei der gleichzeitig Threads einen gemeinsam genutzten Status ändern können, eine ECMAScript- Erweiterung ist .
Benutzer2864740
3

Nun, Chrome ist ein Multiprozess, und ich denke, jeder Prozess befasst sich mit seinem eigenen Javascript-Code, aber soweit der Code weiß, handelt es sich um "Single-Threaded".

In Javascript gibt es keinerlei Unterstützung für Multithreading, zumindest nicht explizit, sodass es keinen Unterschied macht.

Francisco Soto
quelle
2

Ich habe das Beispiel von @ bobince mit geringfügigen Änderungen ausprobiert:

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

Wenn Sie also auf Ausführen klicken, das Alarm-Popup schließen und einen "einzelnen Thread" ausführen, sollte Folgendes angezeigt werden:

click begin
click end
result = 20, should be 20

Wenn Sie jedoch versuchen, dies in Opera oder Firefox unter Windows stabil auszuführen und das Fenster mit dem Warn-Popup auf dem Bildschirm zu minimieren / maximieren, wird Folgendes angezeigt:

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

Ich möchte nicht sagen, dass dies "Multithreading" ist, aber ein Teil des Codes wurde zu einer falschen Zeit ausgeführt, ohne dass ich dies erwartet habe, und jetzt habe ich einen beschädigten Zustand. Und besser über dieses Verhalten Bescheid wissen.

Anton Alexandrenok
quelle
-4

Versuchen Sie, zwei setTimeout-Funktionen ineinander zu verschachteln, und sie verhalten sich multithreaded (dh der äußere Timer wartet nicht auf den Abschluss der inneren, bevor er seine Funktion ausführt).

James
quelle
5
Chrome macht das richtig, keine Ahnung, wo @James sieht, dass es Multithreading ist ...: setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(Für die Aufzeichnung sollte yo dawg IMMER zuerst kommen, dann die Konsolenprotokollausgabe)
Tor Valamo