Maison  >  Article  >  interface Web  >  Mécanisme d'exécution JavaScript et boucle d'événements dans l'environnement nodejs

Mécanisme d'exécution JavaScript et boucle d'événements dans l'environnement nodejs

不言
不言avant
2019-04-02 10:59:592490parcourir

Cet article vous présente une introduction à l'utilisation des fonctions JavaScript de haut niveau. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

1. Description

nodejs est exécuté dans un seul thread et est basé sur un modèle de programmation IO non bloquant basé sur les événements. Cela nous permet de continuer à exécuter le code sans attendre le retour du résultat de l'opération asynchrone. Lorsqu'un événement asynchrone est déclenché, le thread principal sera averti et le thread principal exécutera le rappel de l'événement correspondant.

Cet article explique le processus d'exécution du code JavaScript dans le nœud. Ce qui suit est le code du test. Si vous connaissez le résultat de sortie, vous n'avez pas besoin de lire cet article. résultat de sortie, puis cette vidéo L'article peut vous aider à comprendre :

console.log(1)
setTimeout(function () {
  new Promise(function (resolve) {
    console.log(2)
    resolve()
  })
  .then(() => { console.log(3) })
})
setTimeout(function () {
  console.log(4)
})

Complexe :

setTimeout(() => {
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})
  .then(() => { console.log('5') })
  setTimeout(() => { 
    console.log('6')
    setTimeout(() => {
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)
  })
  setTimeout(() => { console.log('13') }, 0)
})
setTimeout(() => { console.log('14') }, 0)
new Promise((resolve) => { console.log('15'); resolve() })
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })
.then(() => { console.log('18') })

Processus de démarrage de Node.js

Processus de démarrage de node.js Il peut être divisé en les étapes suivantes :

  1. Appelez la méthode platformInit pour initialiser l'environnement d'exécution de nodejs.
  2. Appelez la méthode performance_node_start pour effectuer des statistiques de performances sur nodejs.
  3. Jugement des paramètres openssl.
  4. Appelez v8_platform.Initialize pour initialiser le pool de threads libuv.
  5. Appelez V8::Initialize pour initialiser l'environnement V8.
  6. Créez une instance en cours d'exécution de nodejs.
  7. Démarrez l'instance créée à l'étape précédente.
  8. Commencez à exécuter le fichier js. Une fois le code de synchronisation exécuté, entrez dans la boucle d'événements.
  9. Lorsqu'il n'y a aucun événement à écouter, l'instance nodejs est détruite et l'exécution du programme est terminée.

Mécanisme dexécution JavaScript et boucle dévénements dans lenvironnement nodejs

3. Explication détaillée de la boucle d'événements nodejs

Nodejs subdivise la boucle de messages en 6 étapes (phase appelée officielle ), chaque étape aura une structure de type file d'attente qui stocke les fonctions de rappel qui doivent être traitées dans cette étape

Nodejs est conçu pour empêcher qu'un trop grand nombre de tâches dans une certaine étape ne provoquent une famine dans les étapes suivantes. , donc à chaque itération de la boucle de messages, il y a un nombre maximum de rappels exécutés dans chaque étape. Si le nombre est dépassé, l'étape en cours sera terminée de force et l'étape suivante sera entrée. Cette règle s'applique à chaque rappel. la boucle de message. Une étape.

3.1 Étape de minuterie

Il s'agit de la première étape de la boucle de message, utilisant une boucle for pour traiter tous les rappels setTimeout et setInterval.

Ces rappels sont stockés dans un tas minimum (min tas). De cette façon, le moteur n'a besoin que de juger l'élément d'en-tête à chaque fois, et s'il remplit les conditions, il ne sera pas exécuté. terminez la phase Timer jusqu'à ce qu'il rencontre un élément qui ne remplit pas les conditions ou que la file d'attente soit vide.

La méthode pour déterminer si un certain rappel remplit les conditions de l'étape Timer est également très simple à chaque fois. la boucle de message entre dans la minuterie, elle enregistrera l'heure du système à ce moment-là, puis il suffit de regarder les paramètres de la fonction de rappel dans le tas minimum ci-dessus. Si le temps de démarrage dépasse le temps enregistré lors de l'entrée dans la minuterie, s'il dépasse, prenez-le et exécutez-le

3.2 Phase de rappel d'E/S en attente

exécute à l'exception de close callbacks, setTimeout() et setInterval(), presque tous les rappels à l'exception des rappels setImmediate(), tels que TCP连接发生错误, fs.read, socket et d'autres fonctions de rappel pour les opérations d'E/S, ainsi que divers rappels d'erreur

3.3 Idle, phase de préparation

Quelques appels au sein du système.

3.4 Phase d'interrogation, phase importante

C'est la phase la plus importante de tout le cycle des messages. Son rôle est d'attendre les requêtes et les données asynchrones, car elle prend en charge l'ensemble du mécanisme du cycle des messages.

La phase d'interrogation a deux fonctions principales : l'une consiste à exécuter le rappel des minuteries dont la durée limite inférieure a été atteinte, et l'autre est de traiter les événements dans la file d'attente d'interrogation.
Remarque : De nombreuses API de Node sont basées sur l'abonnement à des événements, telles que fs.readFile. Ces rappels doivent être effectués à l'étape poll.

Lorsque la boucle d'événements entre dans la phase d'interrogation :

  • pollLorsque la file d'attente n'est pas vide, la boucle d'événements doit d'abord parcourir la file d'attente et exécuter le rappel de manière synchrone jusqu'à ce que la file d'attente est effacé ou le rappel est exécuté. Le nombre atteint la limite du système.
  • pollLorsque la file d'attente est vide, il y a deux situations.

    • Si le code a été défini avec un rappel par setImmediate(), alors la boucle d'événements termine directement la phase poll et entre dans la phase check pour exécuter le rappel dans le check file d'attente.
    • Si le code n'a pas été défini setImmediate() Définir le rappel :

      • Si des minuteries sont définies, la boucle d'événements vérifiera les minuteries à ce moment-là. Si une ou plusieurs minuteries ont atteint la limite inférieure, la boucle d'événements reviendra à l'étape des minuteries et exécutera l'effet. rappel de la file d'attente des timers.
      • Si les minuteries ne sont pas définies, la boucle d'événements à ce moment-là est bloquante en attendant que le rappel d'événement soit ajouté à la file d'attente d'interrogation lors de la phase d'interrogation.

Phase d'interrogation, lorsque les rappels d'événements enregistrés par le code de la couche js ne reviennent pas, la boucle d'événements sera temporairement bloquée dans la phase d'interrogation et Conditions débloquées :

  1. Lorsque la phase d'interrogation est exécutée, un délai d'attente sera transmis, qui est le temps de blocage maximum de la phase d'interrogation.
  2. Lorsque le délai d'attente n'a pas expiré, si un événement revient, la fonction de rappel enregistrée pour l'événement sera exécutée. Lorsque le délai d'attente expire, la phase d'interrogation se termine et la phase suivante est exécutée.

Quel est le paramètre approprié pour ce délai d'attente ? La réponse est la différence entre l'heure de début du rappel à exécuter récemment dans la phase de minuterie et maintenant, en supposant que la différence est exacte. il n'y a rien en attente d'exécution après le rappel de la phase d'interrogation. Donc, le temps d'attente maximum ici est delta. Si un événement réveille la boucle de messages pendant la période, continuez le travail de la phase suivante si rien ne se passe pendant la période. après l'expiration du délai, la boucle de messages doit encore entrer dans la phase suivante, donc la phase Timer de l'itération suivante peut également être exécutée
Nodejs conduit toute la boucle de messages à travers la phase d'interrogation, en attendant les événements IO et l'arrivée du noyau. événements asynchrones.

3.5 Phase de vérification

Cette étape ne gère que la fonction de rappel de setImmediate
Alors pourquoi y a-t-il une étape spéciale pour gérer setImmediate, en termes simples ? car l'étape Poll peut définir des rappels et espérer s'exécuter après l'étape Poll. Cette phase Check est donc ajoutée après la phase Poll

Phase 3.6 Close Callbacks

est spécialement conçue pour gérer certains. rappels de type close. Par exemple, socket.on('close', ...) Il est utilisé pour le nettoyage des ressources

4. Processus de code JS d'exécution de Nodejs et processus de boucle d'événement

1. >Initialiser l'environnement du nœud

Exécuter le code d'entrée

Exécution

Rappel

process.nextTickExécuter les microtâches

2. Entrez dans la boucle d'événement

2.1. Entrez dans la

phase

TimerVérifiez

Si la file d'attente a expiré
    rappels, si c'est le cas, tous les
  • rappels expirés seront exécutés dans Timer ordre croissant. TimerTimer pour vérifier s'il y a des TimerId tâches, si oui, toutes Exécuter
  • pour vérifier s'il y a des microtâches (promesse), si oui, exécuter toutes les process.nextTick
  • pour quitter cette étape
  • 2.2, entrez dans l'étape

Pending I/O Callback vérifie s'il y a un rappel pour

, et si oui, exécute le rappel.
    Si la phase n'est pas quittée
  • Pending I/O CallbackVérifiez s'il y a tâches, si oui, exécutez-les toutes
  • Vérifiez s'il y a des microtâches (promesse), si oui , exécutez-les tous process.nextTick
  • Quitter cette étape
  • 2.3, entrez dans l'
  • étape

Cette étape n'a pas grand-chose à voir avec JavaScript, sautez-laidle,prepare

2.4, entrez

Phase

Vérifiez d'abord s'il y a un rappel inachevé S'il existe, il y a deux situations : Poll

Cas 1 : Il y a un rappel exécutable

Exécutez tous les rappels disponibles (y compris les minuteries expirées et certains événements IO, etc.)

Vérifiez s'il y a des

tâches, si c'est le cas, exécutez-les toutes

Vérifiez s'il y a sont des microtâches (promesse ), si c'est le cas, exécutez tout

process.nextTick

Quitter cette étape


Deuxième cas : Il n'y a pas de rappel exécutable

Vérifiez s'il y a un

rappel, si Oui, quittez la phase Poll. Sinon, bloquez dans cette étape, en attendant une nouvelle notification d'événement

S'il n'y a pas de rappel inachevé, quittez l'étape de sondageimmediate

2.5, entrez dans l'étape


S'il y a un rappel immédiat, exécutez tous les rappels immédiats

check pour vérifier s'il y a des

tâches. S'il y en a, exécutez tous les

pour vérifier. s'il y a des microtâches (promesse ), s'il y en a, exécutez toutes process.nextTick
Quitter cette phase

2.6, entrez dans la

phase

S'il y a un rappel immédiat, exécuter tous les rappels immédiats

closing Vérifiez s'il y a des

tâches, si oui, exécutez-les toutes

Vérifiez s'il y a des microtâches (promesse), s'il y en a, exécutez-les toutes

process.nextTickQuittez cette étape

3, vérifiez s'il y a un actif

Si c'est le cas, passez au tour suivant de la boucle d'événement

handles(定时器、IO等事件句柄)

Si non, terminez la boucle d'événements et quittez le programme


Remarque :

Le processus suivant sera exécuté dans l'ordre avant chaque sous-étape de la boucle d'événements se termine : Vérifiez s'il y a un rappel process.nextTick, si c'est le cas, tous exécutés.

Vérifiez s'il existe des microtâches (promesses), et si oui, exécutez-les toutes.

4.1 关于Promise和process.nextTick

事件循环队列先保证所有的process.nextTick回调,然后将所有的Promise回调追加在后面,最终在每个阶段结束的时候一次性拿出来执行。

此外,process.nextTickPromise回调的数量是受限制的,也就是说,如果一直往这个队列中加入回调,那么整个事件循环就会被卡住

Mécanisme dexécution JavaScript et boucle dévénements dans lenvironnement nodejs

4.2 关于setTimeout(…, 0) 和 setImmediate

这两个方法的回调到底谁快?

如下面的例子:

setImmediate(() => console.log(2))
setTimeout(() => console.log(1))

使用nodejs多次执行后,发现输出结果有时是1 2,有时是2 1

对于多次执行输出结果不同,需要了解事件循环的基础问题。

首先,Nodejs启动,初始化环境后加载我们的JS代码(index.js).发生了两件事(此时尚未进入消息循环环节):

setImmediate 向 Check 阶段 中添加了回调 console.log(2);

setTimeout 向 Timer 阶段 中添加了回调 console.log(1)

这时候, 要初始化阶段完毕, 要进入 Nodejs 消息循环了。

为什么会有两种输出呢? 接下来一步很关键:

当执行到 Timer 阶段 时, 会发生两种可能. 因为每一轮迭代刚刚进入 Timer 阶段 时会取系统时间保存起来, 以 ms(毫秒) 为最小单位.

如果 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间, 则执行 Timer 阶段 中的该回调. 这种情况下先输出 1, 直到 Check 阶段 执行后,输出2.总的来说, 结果是 1 2.

如果运行比较快, Timer 阶段 中回调预设的时间可能刚好等于消息循环所保存的时间, 这种情况下, Timer 阶段 中的回调得不到执行, 则继续下一个 阶段. 直到 Check 阶段, 输出 2. 然后等下一轮迭代的 Timer 阶段, 这时的时间一定是满足 Timer 阶段 中回调预设的时间 > 消息循环所保存的时间 , 所以 console.log(1) 得到执行, 输出 1. 总的来说, 结果就是 2 1.

所以, 输出不稳定的原因就取决于进入 Timer 阶段 的时间是否和执行 setTimeout 的时间在 1ms 内. 如果把代码改成如下, 则一定会得到稳定的输出:

require('fs').readFile('my-file-path.txt', () => {
 setImmediate(() => console.log(2))
 setTimeout(() => console.log(1))
});

这是因为消息循环在 Pneding I/O Phase 才向 Timer 和 Check 队列插入回调. 这时按照消息循环的执行顺序, Check 一定在 Timer 之前执行。

从性能角度讲, setTimeout 的处理是在 Timer Phase, 其中 min heap 保存了 timer 的回调, 因此每执行一个回调的同时都会涉及到堆调整. 而 setImmediate 仅仅是清空一个队列. 效率自然会高很多.

再从执行时机上讲. setTimeout(..., 0) 和 setImmediate 完全属于两个阶段.

5. 一个实际例子演示

下面以一段代码来说明nodejs运行JavaScript的机制。

如下面一段代码:

setTimeout(() => {                                                // settimeout1
  console.log('1')
  new Promise((resolve) => { console.log('2'); resolve(); })      // Promise3
  .then(() => { console.log('3') })
  new Promise((resolve)=> { console.log('4'); resolve()})         // Promise4
  .then(() => { console.log('5') })
  setTimeout(() => {                                              // settimeout3
    console.log('6')
    setTimeout(() => {                                            // settimeout5
      console.log('7')
      new Promise((resolve) => { console.log('8'); resolve() })   // Promise5
      .then( () => {  console.log('9') })
      new Promise((resolve) => { console.log('10'); resolve() })  // Promise6
      .then(() => {  console.log('11') })
    })
    setTimeout(() => { console.log('12') }, 0)                    // settimeout6
  })
  setTimeout(() => { console.log('13') }, 0)                      // settimeout4
})
setTimeout(() => { console.log('14') }, 0)                        // settimeout2
new Promise((resolve) => { console.log('15'); resolve() })        // Promise1
.then( ()=> { console.log('16') })
new Promise((resolve) => { console.log('17'); resolve() })        // Promise2
.then(() => { console.log('18') })

上面代码执行过程:

node初始化

执行JavaScript代码

遇到setTimeout, 把回调函数放到Timer队列中,记为settimeout1

遇到setTimeout, 把回调函数放到Timer队列中,记为settimeout2

遇到Promise,执行,输出15,把回调函数放到微任务队列,记为Promise1

遇到Promise,执行,输出17,把回调函数放到微任务队列,记为Promise2

代码执行结束,此阶段输出结果:15 17

没有process.nextTick回调,略过

执行微任务

检查微任务队列是否有可执行回调,此时队列有2个回调:Promise1、Promise2

执行Promise1回调,输出16

执行Promise2回调,输出18

此阶段输出结果:16 18

进入第一次事件循环

进入Timer阶段

检查Timer队列是否有可执行的回调,此时队列有2个回调:settimeout1、settimeout2

执行settimeout1回调:

输出1、2、4

添加了2个微任务,记为Promise3、Promise4

添加了2个Timer任务,记为settimeout3、settimeout4

执行settimeout2回调,输出14

Timer队列任务执行完毕

没有process.nextTick回调,略过

检查微任务队列是否有可执行回调,此时队列有2个回调:Promise3、Promise4

按顺序执行2个微任务,输出3、5

此阶段输出结果:1 2 4 14 3 5

Pending I/O Callback阶段没有任务,略过

进入 Poll 阶段

检查是否存在尚未完成的回调,此时有2个回调:settimeout3、settimeout4

Exécuter le rappel settimeout3

Sortie 6

Ajout de 2 tâches de minuterie, enregistrées sous settimeout5, settimeout6

Exécuter le rappel settimeout4, sortie 13

Il y a pas de process.nextTick rappel, ignorer

Il n'y a pas de microtâche, ignorer

Le résultat de sortie de cette étape : 6 13

vérifiez, il n'y a pas de tâches dans la phase de clôture, sautez

et vérifiez s'il y a encore une handles(定时器、IO等事件句柄) active, si oui, passez au prochain tour de boucle d'événement

et entrez dans la deuxième boucle d'événements

Entrez dans la phase Timer

Vérifiez si la file d'attente Timer a des rappels exécutables À ce moment, la file d'attente a 2 rappels : settimeout5, settimeout6
.

Exécuter le rappel settimeout5 :

Sortie 7, 8, 10

Ajout de 2 microtâches, enregistrées comme Promise5, Promise6

Exécuter settimeout6 rappel, sortie 12

n'a pas de rappel process.nextTick, ignorez

pour vérifier si la file d'attente des microtâches a des rappels exécutables. À ce stade, la file d'attente a 2 rappels. : Promise5, Promise6

Exécuter 2 microtâches en séquence, sortie 9, 11

Résultats de sortie à ce stade : 7 8 10 12 9 11

En attente de rappel d'E/S, sondage, vérifiez, il n'y a aucune tâche dans la phase de fermeture, sautez

et vérifiez s'il y a toujours un handles(定时器、IO等事件句柄) actif. S'il n'y en a plus, terminez l'événement. bouclez et quittez le programme.

L'exécution du programme se termine et affiche le résultat : 15 17 16 18 1 2 4 14 3 5 6 13 7 8 10 12 9 11

Mécanisme dexécution JavaScript et boucle dévénements dans lenvironnement nodejs

[Recommandations associées : Tutoriel vidéo JavaScript]

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer