Heim  >  Artikel  >  Web-Frontend  >  Multithreading der Browser-Benutzeroberfläche und Verständnis des zugrunde liegenden JavaScript-Single-Threaded-Betriebsmechanismus

Multithreading der Browser-Benutzeroberfläche und Verständnis des zugrunde liegenden JavaScript-Single-Threaded-Betriebsmechanismus

黄舟
黄舟Original
2017-02-28 14:37:571278Durchsuche

Schon als ich zum ersten Mal JavaScript lernte, wurde ich von dieser Idee „indoktriniert“

JavaScript ist Single-Threaded
Aber als ich während des Lernens fortfuhr Prozess
Ich habe etwas über Timer und asynchrones Laden von Ajax gelernt
Es hat mich einmal an diesem Satz zweifeln lassen
Da JavaScript Single-Threaded ist, warum hat es immer noch asynchrones Laden?
Später erfuhr ich, dass es nicht nur einen JS-Thread im Browser gibt, sondern diesen zusammen mit anderen Threads - Browser-UI-Multithreading

Ich wollte schon lange einen Artikel schreiben So hatte ich einfach das Gefühl, nicht genug zu verstehen, und ich hatte Angst, dass das, was ich schrieb, alle in die Irre führen könnte
Nachdem ich alle Blogbeiträge von Leuten im Internet gelesen hatte, fühlte ich mich noch verwirrter
Aber in Am Ende nahm ich den Mut zusammen und beschloss, darüber zu sprechen ╮(╯_╰)╭
Lassen Sie uns das Problem der JavaScript-Threads jetzt beiseite legen, werfen wir zunächst einen Blick darauf, was ein Browser-UI-Thread ist

Browser-UI-Multithreading

Zuerst gehen wir zum vorherigen Bild

Multithreading der Browser-Benutzeroberfläche und Verständnis des zugrunde liegenden JavaScript-Single-Threaded-Betriebsmechanismus

Im Allgemeinen haben Browser mindestens drei residente Threads

  • GUI-Rendering-Thread(Rendering-Seite)

  • JS-Engine-Thread(Verarbeitungsskript)

  • Ereignisauslösender Thread (Kontrollinteraktion)

Einschließlich Browser, die manchmal neue Threads öffnen, z. B. HTTP-Anforderungsthreads (Ajax), die ausgelöst werden nach Gebrauch weg, und andere Threads
Sie bilden zusammen den UI-Thread des Browsers
Diese Threads funktionieren ordnungsgemäß unter der Kontrolle des UI-Threads
Online-Meinungen zu diesem residenten Thread sind inkonsistent und die Implementierung von Jeder Browser kann anders sein, daher werde ich hier nicht darauf eingehen

Obwohl ich den JS-Engine-Thread in der unteren rechten Ecke platziert habe, ist er der Haupt-Thread des Browsers
Und er ist nicht kompatibel mit Der GUI-Rendering-Thread kann nicht gleichzeitig funktionieren
Der Grund ist einfach, weil sie alle auf dem DOM arbeiten. Wenn der JS-Thread einen bestimmten DOM-Stil möchte, muss die Rendering-Engine aufhören zu arbeiten (der überhebliche Präsident fordert Sie dazu auf). stehen Sie da und bewegen Sie sich nicht)

js Single Thread

Warum JavaScript Single-Threaded ist
Single-Threaded bedeutet, dass es nur eine Sache gleichzeitig tun kann
Ist Multithreading in JavaScript also nicht schlecht?
Nicht gut
JS ist für die Interaktion mit Benutzern konzipiert, die DOM verarbeiten.
Wenn JavaScript Multithreading ist, muss es das Problem lösen der Multithread-Synchronisation (ich erinnere mich vage an den Schrecken, von der C++-Thread-Synchronisation dominiert zu werden)
Wenn js multithreaded ist und ein Thread gleichzeitig das DOM ändern möchte, wenn ein anderer Thread das DOM löschen möchte
Das Problem wird viel komplizierter. Der Browser weiß nicht, auf wen er hören soll. Wenn ein „Sperr“-Mechanismus eingeführt wird, wird es sehr mühsam (dann lerne ich das Front-End nicht ( ̄_). ,  ̄ )) ​​
Eine Skriptsprache wie unsere muss also nicht so kompliziert sein, daher ist JavaScript seit seiner Geburt Single-Threaded.

Obwohl H5 Web Worker vorgeschlagen hat, kann es das DOM nicht bedienen , also muss es noch einem großen Bruder anvertraut werden, um den js-Hauptthread zu lösen
Ich weiß nicht viel über diesen Web Worker, daher werde ich hier nicht viel sagen
Diese Unterthreads werden vollständig von gesteuert Haupt-Thread großer Bruder, daher ändern sie nicht die Single-Threaded-Natur von JavaScript

Ausführungsstapel

Werfen wir zunächst einen Blick darauf, was ein Ausführungsstapel ist
Der Stapel ist ein Novum -in, last-out (FILO) Datenstruktur
Der Ausführungsstapel speichert die ausgeführten Aufgaben, jede Aufgabe wird als „Frame“ bezeichnet
Zum Beispiel

function foo(c){
    var a = 1;
    bar(200);
}function bar(d){
    var b = 2;
}
foo(100);

Lassen Sie uns einen Blick darauf werfen wie sich der Ausführungsstapel geändert hat

  • Zu Beginn, wenn der Code nicht ausgeführt wird, ist der Ausführungsstapel ein leerer Stapel

  • Wenn die foo Wenn die Funktion ausgeführt wird, wird ein Frame erstellt, der formale Parameter und lokale Variablen enthält (Vorkompilierungsprozess), und dann wird dieser Frame komprimiert. Schieben Sie

  • in den Stapel und führen Sie dann den Code im foo aus Funktion: Führen Sie die Balkenfunktion

  • aus, um einen neuen Rahmen zu erstellen, der auch formale Parameter und lokale Variablen enthält. Drücken Sie die Balkenfunktion

  • Der Stapel ist abgeschlossen und der Stapel

  • foo-Funktion ist abgeschlossen und der Stapel

  • Der Ausführungsstapel entspricht tatsächlich dem js-Hauptthread

Aufgabenwarteschlange

Die Warteschlange ist eine First-In-First-Out-Datenstruktur (FIFO).

Es gibt auch eine Aufgabenwarteschlange im js-Thread

Die Aufgabenwarteschlange enthält eine Reihe von Aufgaben, die verarbeitet werden müssen
Einzelner Thread bedeutet, dass alle Aufgaben nacheinander ausgeführt werden müssen. Wenn die Ausführungszeit von a Die Aufgabe ist zu lang. Wenn das Kind vorne weiter mit der Nadel rollt, müssen die nachfolgenden Aufgaben warten
Es ist, als würde eine Krankenschwester den Kindern in der Reihe eine Spritze geben, wenn das Kind vorne weiter mit der Nadel rollt , das Kind hinten muss warten (diese Metapher scheint nicht zu funktionieren)
Aber wenn das Kind vorne durch die Nadel ohnmächtig wird
dann kann die Krankenschwester nicht dort sitzen, wenn es aufwacht Wenn er aufsteht, muss er dem Kind zuerst eine Injektion in den Rücken geben
, was gleichbedeutend mit der Injektion dieses Kindes „Ausstehend“ (asynchron) ist

Aufgaben können also in zwei Arten unterteilt werden

    Synchrone Aufgaben
  • Asynchrone Aufgaben

同步任务就是正在主线程执行栈中执行的任务(在屋子内打针的小朋友)
而异步任务是在任务队列等候处理的任务(在屋子外等候打针的小朋友)
一旦执行栈中没有任务了,它就会从执行队列中获取任务执行

事件与回调

任务队列是一个事件的队列,IO设备(输入/输出设备)每完成一项任务,就会在任务队列中添加事件处理
用户触发了事件,也同样会将回调添加到任务队列中去
主线程执行异步任务,便是执行回调函数(事件处理函数)
只要执行栈一空,排在执行队列前面的会被优先读取执行,
不过主线程会检查时间,某些事件需要到了规定时间才能进入主线程处理(定时器事件)

事件循环

主线程从执行队列不断地获取任务,这个过程是循环不断地,叫做“Event Loop”事件循环
同步任务总是会在异步任务之前执行
只有当前的脚本执行完,才能够去拿任务队列中的任务执行
前面也说到了,任务队列中的事件可以是定时器事件
定时器分为两种 setTimeout() 和 setInterval()
前者是定时执行一次,后者定时重复执行
第一个参数为执行的回调函数,第二个参数是间隔时间(ms)
来看这样一个例子

setTimeout(function(){
    console.log('timer');
},1000);console.log(1);console.log(2);console.log(3);

这个没什么问题,浏览器打印的是 1 2 3 timer
但是这样呢

setTimeout(function(){
    console.log('timer');
},0);//0延时console.log(1);
console.log(2);
console.log(3);

浏览器打印依然打印的是 1 2 3 timer
也许有同学知道,旧版浏览器,setTimeout定时至少是10ms(即便你设置了0ms),
H5新规范是定时至少4ms(我读书少不知道为什么),改变DOM也是至少16ms
也许这是因为这个原因
那么我再改动一下代码

setTimeout(function(){
    console.log('timer');
},0);var a = +new Date();for(var i = 0; i < 1e5; i++){
    console.log(1);
}var b = +new Date();
console.log(b - a);

这回够刺激了吧,输出10w次,我浏览器都假死了(心疼我chrome)
不仅如此,我还打印了循环所用时间
来看看控制台

输出了10w个1,用了将近7s
timer依然是最后打印的
这就证明了我前面说的话: 同步任务总是会在异步任务之前执行
只有我执行栈空了,才会去你任务队列中取任务执行

实例

最后我举一个例子加深一下理解

demo.onclick = function(){
    console.log(&#39;click&#39;);
}function foo(a){
    var b = 1;
    bar(200);
}function bar(c){
    var d = 2;
    click//伪代码 此时触发了click事件(这里我假装程序运行到这里手动点击了demo)
    setTimeout(function(){
        console.log(&#39;timer&#39;);
    }, 0);
}
foo(100);

怕大家蒙我就不写Ajax了
Ajax如果处理结束后(通过Http请求线程),也会将回调函数放在任务队列中
还有一点click那一行伪代码我最开始是想用demo.click()模拟触发事件
后来在测试过程中,发现它好像跟真实触发事件不太一样
它应该是不通过触发事件线程,而是存在于执行栈中,就相当于单纯地执行click回调函数
不过这只是我自己的想法有待考证,不过这不是重点,重点是我们理解这个过程,请大家不要吐槽我╰( ̄▽ ̄)╭

下面看看执行这段代码时发生了什么(主要说栈和队列的问题,不会赘述预编译过程)

  • 主线程开始执行,产生了栈、堆、队列

  • demo节点绑定了事件click,交给事件触发线程异步监听

  • 执行foo函数(之前同样有预编译过程),创建了帧包括foo函数的形参、局部变量压入执行栈中

  • foo函数内执行bar函数,创建帧包括bar函数的形参、局部变量压入执行栈中

  • 触发了click事件,事件触发线程将回调事件处理函数放到js线程的任务队列中

  • 触发了定时器事件,事件触发线程立即(4ms)将回调处理函数放到js线程的任务队列中

  • bar函数执行完毕,弹出栈

  • foo函数执行完毕,弹出栈

  • 此时执行栈为空

  • 执行栈向任务队列中获取一个任务:click回调函数,输出‘click’

  • 执行栈项任务队列中获取一个任务:定时器回调函数,输出‘timer’

  • 执行结束

这里从任务队列里不断取任务的过程就是Event Loop

Event Loop

有一些我的理解,如果发现不对或者有疑问的地方,请联系我
相信大家看了这个例子应该对js底层运行机制有了一个大概的了解

 以上就是Multithreading der Browser-Benutzeroberfläche und Verständnis des zugrunde liegenden JavaScript-Single-Threaded-Betriebsmechanismus及对JavaScript单线程底层运行机制的理解的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn