Maison  >  Article  >  interface Web  >  Multithreading de l'interface utilisateur du navigateur et compréhension du mécanisme d'exploitation sous-jacent JavaScript monothread

Multithreading de l'interface utilisateur du navigateur et compréhension du mécanisme d'exploitation sous-jacent JavaScript monothread

黄舟
黄舟original
2017-02-28 14:37:571273parcourir

Dès que j'ai appris JavaScript, j'ai été "endoctriné" par cette idée

JavaScript est monothread
Mais au fur et à mesure que j'ai continué pendant l'apprentissage processus
J'ai découvert les minuteries et le chargement asynchrone d'ajax
Cela m'a fait douter une fois de cette phrase
Puisque JavaScript est monothread, pourquoi a-t-il toujours un chargement asynchrone ?
Plus tard, j'ai appris qu'il n'y avait pas seulement un fil js dans le navigateur, mais celui-ci avec d'autres fils - Multi-threading de l'interface utilisateur du navigateur

J'ai longtemps voulu écrire un article comme ça, j'avais juste l'impression de ne pas assez comprendre, et j'avais peur que ce que j'écrivais puisse induire tout le monde en erreur
Après avoir lu tous les articles de blog écrits par tout le monde sur Internet, je me sentais encore plus confus
Mais dans à la fin, j'ai rassemblé mon courage et j'ai décidé d'en parler. ╮(╯_╰)╭
Laissons de côté la question des threads JavaScript maintenant, jetons d'abord un coup d'œil à ce qu'est un fil d'interface utilisateur de navigateur

Multi-threading de l'interface utilisateur du navigateur

Tout d'abord, passons à l'image précédente

Multithreading de linterface utilisateur du navigateur et compréhension du mécanisme dexploitation sous-jacent JavaScript monothread

Généralement, les navigateurs ont au moins trois threads résidents

  • Fil de rendu GUI(Page de rendu)

  • Fil du moteur JS(Script de traitement)

  • Fil de déclenchement d'événement (Interaction de contrôle)

Y compris les navigateurs qui ouvrent parfois de nouveaux fils, comme le fil de requête Http (Ajax) qui est jetés après utilisation, et d'autres fils
Ils constituent ensemble le fil d'interface utilisateur du navigateur
Ces fils fonctionnent de manière ordonnée sous le contrôle du fil d'interface utilisateur
Les opinions en ligne sur ce fil résident sont incohérentes et la mise en œuvre de chaque navigateur peut être différent, donc je n'entrerai pas dans les détails ici

Bien que j'aie mis le fil du moteur js dans le coin inférieur droit, c'est le fil principal du navigateur
Et il est incompatible. avec le thread de rendu GUI et ne peut pas fonctionner en même temps
La raison est simple, car ils doivent tous faire fonctionner le DOM. Si le thread js veut un certain style DOM, le moteur de rendu doit cesser de fonctionner (demande le président autoritaire). vous de rester là et de ne pas bouger)

js single thread

Pourquoi JavaScript est à thread unique
Single-thread signifie qu'il ne peut faire qu'une seule chose à la fois
Alors le multithreading en JavaScript n'est-il pas mauvais ? Quelle est son efficacité ?
Pas bon
JS est conçu pour interagir avec le traitement du DOM
Si JavaScript est multithread, il doit le gérer. le problème de la synchronisation multi-thread (je me souviens vaguement de l'horreur d'être dominé par la synchronisation des threads C)
Si js est multi-thread, et qu'un thread veut modifier le DOM en même temps, Quand un autre thread veut supprimer le DOM
, le problème devient beaucoup plus compliqué. Le navigateur ne sait pas qui écouter Si un mécanisme de "verrouillage" est introduit, ce sera très gênant (alors je n'apprendrai pas le front-end ().  ̄_,  ̄ )) ​​
Il n'est donc pas nécessaire qu'un langage de script comme le nôtre soit aussi compliqué, donc JavaScript est monothread depuis sa naissance

Bien que H5 ait proposé Web Worker, il ne peut pas fonctionner le DOM, il faut donc encore confier à un grand frère la résolution du problème du thread principal js
Je ne connais pas grand chose à ce Web Worker, donc je ne dirai pas grand chose ici
Ces sous-threads sont complètement contrôlés par le frère du thread principal, afin qu'ils ne changent pas la nature monothread de JavaScript

Pile d'exécution

Jetons d'abord un coup d'œil à ce qu'est une pile d'exécution
La pile est une structure de données premier entré, dernier sorti (FILO)
La pile d'exécution stocke les tâches en cours d'exécution, chaque tâche C'est ce qu'on appelle un « frame »
Par exemple

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

Prenons un regardez comment la pile d'exécution a changé

  • Au début, lorsque le code n'est pas exécuté, la pile d'exécution est une pile vide

  • Quand la fonction foo est exécutée, un cadre est créé. Ce cadre contient des paramètres formels et des variables locales (processus de précompilation), puis ce cadre est pressé Push

  • dans la pile, puis exécutez le codez dans la fonction foo, exécutez la fonction bar

  • pour créer un nouveau cadre, qui a également des paramètres formels et des variables locales, appuyez sur Le

  • La fonction bar dans la pile est terminée, et la pile

  • la fonction foo est terminée, et la pile

  • La pile d'exécution. est vide

La pile d'exécution est en fait équivalente au thread principal js

File d'attente des tâches

La file d'attente est premier entré, premier sorti ( FIFO) Structure des données

Il existe également une file d'attente des tâches dans le thread js
La file d'attente des tâches contient une série de tâches à traiter
Un seul thread signifie que toutes les tâches doivent être exécutées les unes après les autres. le temps d'exécution d'une tâche est trop long, Si l'enfant devant continue de faire rouler l'aiguille, alors les tâches suivantes devront attendre
C'est comme une infirmière faisant une injection aux enfants en file d'attente si l'enfant est devant. continue de faire rouler l'aiguille, l'enfant à l'arrière devra attendre (cette métaphore ne semble pas fonctionner) Approprié)
Mais si l'enfant à l'avant s'évanouit à cause de l'aiguille
alors la tante infirmière ne le sera pas capable de s'asseoir là. Quand il se réveille, il doit d'abord donner l'aiguille à l'enfant dans le dos
ce qui équivaut à injecter à cet enfant "En attente" (asynchrone)

Ainsi, les tâches peuvent être divisées. en deux types

  • Tâches synchrones

  • Tâches asynchrones

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

事件与回调

任务队列是一个事件的队列,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 de linterface utilisateur du navigateur et compréhension du mécanisme dexploitation sous-jacent JavaScript monothread及对JavaScript单线程底层运行机制的理解的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn