Javascript ist keine Multi-Thread-Sprache, sondern eine Single-Thread-Sprache. JavaScript ist eine Browser-Skriptsprache und ihr Interpreter ist Single-Threaded. Der Hauptzweck von JavaScript besteht darin, mit Benutzern zu interagieren und das DOM zu bedienen. Dies bedeutet, dass es nur Single-Threaded sein kann, da es sonst zu sehr komplexen Synchronisierungsproblemen kommt .
Die Betriebsumgebung dieses Tutorials: Windows 7-System, JavaScript-Version 1.8.5, Dell G3-Computer.
Javascript ist keine Multi-Thread-Sprache, sondern eine Single-Thread-Sprache. Die JavaScript-Sprache unterstützt auch kein Multithreading, da der JavaScript-Interpreter im Browser Single-Threaded ist.
Ein Hauptmerkmal der JavaScript-Sprache ist, dass sie Single-Threaded ist, was bedeutet, dass sie jeweils nur eine Sache ausführen kann.
Warum kann JavaScript also nicht mehrere Threads haben? Dadurch kann die Effizienz verbessert werden.
JavaScript ist je nach Verwendungszweck Single-Threaded. Als Browser-Skriptsprache besteht der Hauptzweck von JavaScript darin, mit Benutzern zu interagieren und das DOM zu manipulieren. Dies legt fest, dass es nur Single-Threaded sein kann, da es sonst zu sehr komplexen Synchronisationsproblemen kommt.
Um die Rechenleistung von Multi-Core-CPUs zu nutzen, schlägt HTML5 den Web Worker-Standard vor, der es JavaScript-Skripten ermöglicht, mehrere Threads zu erstellen, die untergeordneten Threads werden jedoch vollständig vom Haupt-Thread gesteuert und dürfen das DOM nicht bedienen . Daher ändert dieser neue Standard nichts an der Single-Threaded-Natur von JavaScript.
Aufgabenwarteschlange
Einzelner Thread bedeutet, dass alle Aufgaben in die Warteschlange gestellt werden müssen und die nächste Aufgabe erst ausgeführt wird, wenn die vorherige Aufgabe abgeschlossen ist. Wenn die vorherige Aufgabe lange dauert, muss die nächste Aufgabe warten.
Wenn die Warteschlange auf einen großen Rechenaufwand zurückzuführen ist und die CPU zu beschäftigt ist, vergessen Sie es, aber oft ist die CPU im Leerlauf, weil das E/A-Gerät (Eingabe- und Ausgabegerät) sehr langsam ist (z. B. Ajax-Operationen, die Daten lesen). aus dem Netzwerk). Ich muss warten, bis die Ergebnisse vorliegen, bevor ich fortfahre.
Die Designer der JavaScript-Sprache haben erkannt, dass der Hauptthread zu diesem Zeitpunkt das E/A-Gerät vollständig ignorieren, die wartenden Aufgaben anhalten und die späteren Aufgaben zuerst ausführen kann. Warten Sie, bis das E/A-Gerät das Ergebnis zurückgibt, gehen Sie dann zurück und fahren Sie mit der Ausführung der angehaltenen Aufgabe fort.
Daher können alle Aufgaben in zwei Typen unterteilt werden: eine ist eine synchrone Aufgabe (synchron) und die andere ist eine asynchrone Aufgabe (asynchron).
Synchronische Aufgaben beziehen sich auf Aufgaben, die zur Ausführung im Hauptthread in die Warteschlange gestellt werden. Die nächste Aufgabe kann erst ausgeführt werden, nachdem die vorherige Aufgabe ausgeführt wurde.
Asynchrone Aufgaben beziehen sich auf Aufgaben, die nicht in den Hauptthread, sondern in die „Aufgabenwarteschlange“ gelangen " (Aufgabenwarteschlange), nur wenn die „Aufgabenwarteschlange“ den Hauptthread benachrichtigt, dass eine asynchrone Aufgabe ausgeführt werden kann, wird die Aufgabe zur Ausführung in den Hauptthread eingegeben.
Im Einzelnen ist der Betriebsmechanismus der asynchronen Ausführung wie folgt. (Dasselbe gilt für die synchrone Ausführung, da sie als asynchrone Ausführung ohne asynchrone Aufgaben betrachtet werden kann.)
(1) Alle synchronen Aufgaben werden im Hauptthread ausgeführt und bilden einen Ausführungskontextstapel.
(2) Zusätzlich zum Hauptthread gibt es auch eine „Aufgabenwarteschlange“. Solange die asynchrone Aufgabe laufende Ergebnisse hat, wird ein Ereignis in die „Aufgabenwarteschlange“ gestellt.
(3) Sobald alle Synchronisierungsaufgaben im „Ausführungsstapel“ abgeschlossen sind, liest das System die „Aufgabenwarteschlange“, um zu sehen, welche Ereignisse darin enthalten sind. Diese entsprechenden asynchronen Aufgaben beenden den Wartezustand, betreten den Ausführungsstapel und beginnen mit der Ausführung.
(4) Der Hauptthread wiederholt weiterhin den dritten Schritt oben.
Das Bild unten ist ein schematisches Diagramm des Hauptthreads und der Aufgabenwarteschlange.
Solange der Hauptthread leer ist, wird die „Aufgabenwarteschlange“ gelesen. Dies ist der laufende Mechanismus von JavaScript. Dieser Vorgang wiederholt sich ständig.
Ereignisse und Rückruffunktionen
„Aufgabenwarteschlange“ ist eine Warteschlange von Ereignissen (kann auch als Warteschlange von Nachrichten verstanden werden). Wenn das IO-Gerät eine Aufgabe abschließt, wird ein Ereignis zur „Aufgabenwarteschlange“ hinzugefügt Stellen Sie die relevanten asynchronen Aufgaben dar und können Sie sie in den „Ausführungsstapel“ eingeben. Der Hauptthread liest die „Aufgabenwarteschlange“, was bedeutet, dass die darin enthaltenen Ereignisse gelesen werden.
Zu den Ereignissen in der „Aufgabenwarteschlange“ gehören neben IO-Geräteereignissen auch einige benutzergenerierte Ereignisse (z. B. Mausklicks, Seitenscrollen usw.). Solange die Rückruffunktion angegeben ist, werden diese Ereignisse bei ihrem Auftreten in die „Aufgabenwarteschlange“ eingegeben und warten auf das Lesen durch den Hauptthread.
Die sogenannte „Rückruffunktion“ (Callback) ist der Code, der vom Hauptthread aufgehängt wird. Asynchrone Aufgaben müssen eine Rückruffunktion angeben. Wenn der Hauptthread mit der Ausführung einer asynchronen Aufgabe beginnt, wird die entsprechende Rückruffunktion ausgeführt.
Die „Aufgabenwarteschlange“ ist eine First-In-First-Out-Datenstruktur. Die zuerst eingestuften Ereignisse werden zuerst vom Hauptthread gelesen. Der Lesevorgang des Hauptthreads erfolgt grundsätzlich automatisch. Sobald der Ausführungsstapel gelöscht wird, gelangt das erste Ereignis in der „Aufgabenwarteschlange“ automatisch in den Hauptthread. Aufgrund der später erwähnten „Timer“-Funktion muss der Hauptthread jedoch zunächst die Ausführungszeit überprüfen. Bestimmte Ereignisse können erst nach der angegebenen Zeit zum Hauptthread zurückkehren.
Ereignisschleife
Der Hauptthread liest Ereignisse aus der „Aufgabenwarteschlange“. Dieser Prozess ist zyklisch, daher wird der gesamte Betriebsmechanismus auch als Ereignisschleife bezeichnet.
Um Event Loop besser zu verstehen, schauen Sie sich bitte das Bild unten an.
Im Bild oben werden beim Ausführen des Hauptthreads ein Heap und ein Stack generiert. Der Code im Stack ruft verschiedene externe APIs auf und fügt verschiedene Ereignisse hinzu (klicken, laden, fertig). Solange der Code im Stapel ausgeführt wird, liest der Hauptthread die „Aufgabenwarteschlange“ und führt die diesen Ereignissen entsprechenden Rückruffunktionen nacheinander aus.
Der Code im Ausführungsstapel (synchrone Aufgabe) wird immer ausgeführt, bevor die „Aufgabenwarteschlange“ (asynchrone Aufgabe) gelesen wird. Schauen Sie sich das Beispiel unten an.
var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function (){}; req.onerror = function (){}; req.send();
Die req.send-Methode im obigen Code ist eine Ajax-Operation zum Senden von Daten an den Server. Es handelt sich um eine asynchrone Aufgabe, was bedeutet, dass das System die „Aufgabenwarteschlange“ erst liest, wenn alle Codes des aktuellen Skripts vorliegen werden ausgeführt. Daher entspricht es dem folgenden Schreiben.
var req = new XMLHttpRequest(); req.open('GET', url); req.send(); req.onload = function (){}; req.onerror = function (){};
Mit anderen Worten, es spielt keine Rolle, ob der Teil, der die Rückruffunktion angibt (onload und onerror), vor oder nach der send()-Methode liegt, da sie Teil des Ausführungsstapels sind und das System immer ausgeführt wird sie vor dem Lesen „Aufgabenwarteschlange“.
Timer
Zusätzlich zum Platzieren von Ereignissen für asynchrone Aufgaben kann die „Aufgabenwarteschlange“ auch zeitgesteuerte Ereignisse platzieren, d. h. festlegen, wie lange ein bestimmter Code danach ausgeführt wird. Dies wird als „Timer“-Funktion bezeichnet, bei der es sich um Code handelt, der regelmäßig ausgeführt wird.
Die Timer-Funktion wird hauptsächlich durch die beiden Funktionen setTimeout() und setInterval() vervollständigt. Ihre internen Betriebsmechanismen sind genau die gleichen. Der Unterschied besteht darin, dass der von erstere angegebene Code einmal ausgeführt wird, während letzterer wiederholt ausgeführt wird. Im Folgenden wird hauptsächlich setTimeout () erläutert.
setTimeout() akzeptiert zwei Parameter, der erste ist die Rückruffunktion und der zweite ist die Anzahl der Millisekunden, um die die Ausführung verzögert wird.
console.log(1); setTimeout(function(){console.log(2);},1000); console.log(3);
Die Ausführungsergebnisse des obigen Codes sind 1, 3, 2, da setTimeout() die Ausführung der zweiten Zeile auf 1000 Millisekunden später verschiebt.
Wenn der zweite Parameter von setTimeout() auf 0 gesetzt ist, bedeutet dies, dass nach der Ausführung des aktuellen Codes (der Ausführungsstapel wird geleert) die angegebene Rückruffunktion sofort ausgeführt wird (Intervall von 0 Millisekunden)
setTimeout(function(){console.log(1);}, 0); console.log(2);
Die Ausführung Das Ergebnis des obigen Codes ist immer 2, 1, da das System erst nach Ausführung der zweiten Zeile die Rückruffunktion in der „Aufgabenwarteschlange“ ausführt.
Kurz gesagt besteht die Bedeutung von setTimeout (fn, 0) darin, eine Aufgabe anzugeben, die in der frühesten verfügbaren Leerlaufzeit des Hauptthreads ausgeführt werden soll, dh so früh wie möglich. Es fügt am Ende der „Aufgabenwarteschlange“ ein Ereignis hinzu, sodass es erst ausgeführt wird, wenn die Synchronisierungsaufgabe und die vorhandenen Ereignisse in der „Aufgabenwarteschlange“ verarbeitet wurden.
Der HTML5-Standard schreibt vor, dass der Mindestwert (kürzestes Intervall) des zweiten Parameters von setTimeout() nicht weniger als 4 Millisekunden betragen darf. Wenn er niedriger als dieser Wert ist, wird er automatisch erhöht. Zuvor haben ältere Browser das Mindestintervall auf 10 Millisekunden festgelegt. Darüber hinaus werden diese DOM-Änderungen (insbesondere solche, die das erneute Rendern von Seiten beinhalten) normalerweise nicht sofort, sondern alle 16 Millisekunden ausgeführt. Derzeit ist der Effekt der Verwendung von requestAnimationFrame () besser als der von setTimeout ().
Es ist zu beachten, dass setTimeout() das Ereignis nur in die „Aufgabenwarteschlange“ einfügt. Der Hauptthread muss warten, bis die Ausführung des aktuellen Codes (Ausführungsstapels) abgeschlossen ist, bevor der Hauptthread die von ihm angegebene Rückruffunktion ausführt. Wenn der aktuelle Code lange dauert, kann es lange dauern, sodass nicht garantiert werden kann, dass die Rückruffunktion zu dem von setTimeout () angegebenen Zeitpunkt ausgeführt wird.
Ereignisschleife von Node.jsNode.js ist ebenfalls eine Single-Threaded-Ereignisschleife, ihr Betriebsmechanismus unterscheidet sich jedoch von der Browserumgebung.
Bitte schauen Sie sich das schematische Diagramm unten an
Gemäß dem obigen Diagramm ist der Betriebsmechanismus von Node.js wie folgt.
(1) Die V8-Engine analysiert JavaScript-Skripte.
(2) Der analysierte Code ruft die Node-API auf.
(3) Die libuv-Bibliothek ist für die Ausführung der Node-API verantwortlich. Es weist verschiedenen Threads unterschiedliche Aufgaben zu, um eine Ereignisschleife (Ereignisschleife) zu bilden, und gibt die Ausführungsergebnisse der Aufgaben asynchron an die V8-Engine zurück.
(4) Die V8-Engine gibt die Ergebnisse an den Benutzer zurück.
Zusätzlich zu den beiden Methoden setTimeout und setInterval bietet Node.js auch zwei weitere Methoden im Zusammenhang mit der „Aufgabenwarteschlange“: process.nextTick und setImmediate. Sie können uns helfen, unser Verständnis von „Aufgabenwarteschlangen“ zu vertiefen.
Die Methode „process.nextTick“ kann die Rückruffunktion am Ende des aktuellen „Ausführungsstapels“ auslösen – vor der nächsten Ereignisschleife (der Hauptthread liest die „Aufgabenwarteschlange“). Das heißt, die angegebene Aufgabe wird immer vor allen asynchronen Aufgaben ausgeführt. Die setImmediate-Methode fügt ein Ereignis am Ende der aktuellen „Aufgabenwarteschlange“ hinzu, dh die von ihr angegebene Aufgabe wird immer in der nächsten Ereignisschleife ausgeführt, was setTimeout (fn, 0) sehr ähnlich ist. Siehe das Beispiel unten (über StackOverflow).
process.nextTick(function A() { console.log(1); process.nextTick(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0) // 1 // 2 // TIMEOUT FIRED
上面代码中,由于process.nextTick方法指定的回调函数,总是在当前”执行栈”的尾部触发,所以不仅函数A比setTimeout指定的回调函数timeout先执行,而且函数B也比timeout先执行。这说明,如果有多个process.nextTick语句(不管它们是否嵌套),将全部在当前”执行栈”执行。
现在,再看setImmediate。
setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0);
上面代码中,setImmediate与setTimeout(fn,0)各自添加了一个回调函数A和timeout,都是在下一次Event Loop触发。那么,哪个回调函数先执行呢?答案是不确定。运行结果可能是1–TIMEOUT FIRED–2,也可能是TIMEOUT FIRED–1–2。
令人困惑的是,Node.js文档中称,setImmediate指定的回调函数,总是排在setTimeout前面。实际上,这种情况只发生在递归调用的时候。
setImmediate(function (){ setImmediate(function A() { console.log(1); setImmediate(function B(){console.log(2);}); }); setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0); }); // 1 // TIMEOUT FIRED // 2
上面代码中,setImmediate和setTimeout被封装在一个setImmediate里面,它的运行结果总是1–TIMEOUT FIRED–2,这时函数A一定在timeout前面触发。至于2排在TIMEOUT FIRED的后面(即函数B在timeout后面触发),是因为setImmediate总是将事件注册到下一轮Event Loop,所以函数A和timeout是在同一轮Loop执行,而函数B在下一轮Loop执行。
我们由此得到了process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前”执行栈”一次执行完,多个setImmediate可能则需要多次loop才能执行完。事实上,这正是Node.js 10.0版添加setImmediate方法的原因,否则像下面这样的递归调用process.nextTick,将会没完没了,主线程根本不会去读取”事件队列”!
process.nextTick(function foo() { process.nextTick(foo); });
事实上,现在要是你写出递归的process.nextTick,Node.js会抛出一个警告,要求你改成setImmediate。
另外,由于process.nextTick指定的回调函数是在本次”事件循环”触发,而setImmediate指定的是在下次”事件循环”触发,所以很显然,前者总是比后者发生得早,而且执行效率也高(因为不用检查”任务队列”)。
【相关推荐:javascript学习教程】
Das obige ist der detaillierte Inhalt vonIst Javascript eine Multithread-Sprache?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!