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?
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?
Antworten:
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 out
allen 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.onchange
Listener 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, change
und 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 onclick
Handler sofort in allen Browsern auf (zumindest ist dies konsistent!).
(Ich verwende hier die on...
Eigenschaften des direkten Ereignishandlers, aber das gleiche passiert mit addEventListener
und 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 alert
Sie 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 out
in 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 showModalDialog
auf 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 setInterval
Funktion. Wenn Sie ein Framework schreiben, das von komplexen Anwendungen verwendet werden soll, ist dies möglicherweise ein guter Schritt. postMessage
wird hoffentlich auch den Schmerz des Cross-Document-Scripting in Zukunft lindern.
blur
nachdem Ihr Code die Kontrolle an den Browser zurückgegeben hat.
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/setInterval
HTTP-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.)
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:
Im Fall von Browsern und Nodejs (und wahrscheinlich vielen anderen Engines) ist Javascript kein Multithread- Programm, sondern die Engines selbst .
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.
Seien Sie neben der Theorie immer auf mögliche Eckfälle und Fehler vorbereitet, die in der akzeptierten Antwort beschrieben sind
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. setTimeout
und setInterval
sind 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).
@ 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.
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.
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.
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.
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).
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)