Qu'est-ce que la boucle événementielle ? Cet article va vous présenter la boucle d'événements dans Node, j'espère qu'il vous sera utile !
Bien que JavaScript soit monothread, la boucle d'événements utilise autant que possible le noyau du système, permettant à Node.js d'effectuer des opérations d'E/S non bloquantes Bien que la plupart des noyaux modernes soient multithreads, ils peuvent gérer des tâches multithreads en arrière-plan. Lorsqu'une tâche est terminée, le noyau l'informe à Node.js, puis le rappel approprié sera ajouté à la boucle pour exécution. Cet article présentera ce sujet plus en détail
Quand Node. js démarre Une fois exécutée, la boucle d'événements sera d'abord initialisée, le script d'entrée fourni sera traité (ou placé dans le REPL, ce qui n'est pas couvert dans ce document). Cela effectuera un appel d'API asynchrone, planifiera une minuterie ou un appel). process.nextTick(), puis commencez à traiter la boucle d'événements
La figure ci-dessous montre un aperçu simplifié de la séquence d'exécution de la boucle d'événements
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
Chaque case représente une étape de la boucle d'événements
Chaque étape a une file d'attente FIFO exécution du rappel, mais chaque étape est exécutée à sa manière. , En général, lorsque la boucle d'événements entre dans une étape, elle effectue toutes les opérations de l'étape en cours et commence à exécuter des rappels dans la file d'attente de l'étape en cours jusqu'à ce que la file d'attente soit complètement consommée ou que la le nombre maximum de données dans la file d'attente est atteint. Lorsque la file d'attente est épuisée ou atteint sa taille maximale, la boucle d'événements passe à la phase suivante.
Dans chaque processus de la boucle d'événements, Node.js vérifie s'il attend des E/S asynchrones et des timers, sinon puis fermez complètement
Une minuterie spécifie le point critique auquel un rappel sera exécuté, plutôt que l'heure à laquelle on souhaite qu'il s'exécute, la minuterie sera dans le passé spécifié Exécutez-le le plus tôt possible après l'heure prévue, cependant, la planification du système d'exploitation ou d'autres rappels retarderont son exécution.
Techniquement parlant, la phase d'interrogation détermine quand le rappel est exécuté
Par exemple, vous définissez une minuterie pour qu'elle s'exécute après 100 ms, mais votre script lit un fichier de manière asynchrone et prend 95 ms
const fs = require('fs'); function someAsyncOperation(callback) { // Assume this takes 95ms to complete fs.readFile('/path/to/file', callback); } const timeoutScheduled = Date.now(); setTimeout(() => { const delay = Date.now() - timeoutScheduled; console.log(`${delay}ms have passed since I was scheduled`); }, 100); // do someAsyncOperation which takes 95 ms to complete someAsyncOperation(() => { const startCallback = Date.now(); // do something that will take 10ms... while (Date.now() - startCallback < 10) { // do nothing } });
Lorsque l'événement La boucle entre dans l'interrogation phase et est une file d'attente vide (fs.readFile() n'est pas encore terminé), il attendra donc les millisecondes restantes jusqu'à ce que le seuil de minuterie le plus rapide soit atteint. Après 95 ms, fs.readFile() La lecture du fichier est terminée et elle est terminée. Il faudra 10 ms pour terminer l'ajout à la phase d'interrogation et l'exécution. Lorsque le rappel est terminé, il n'y a aucun rappel dans la file d'attente à exécuter et la boucle d'événements revient à la phase des minuteries pour exécuter le rappel du minuteur. Dans cet exemple, vous verrez que le timer est retardé de 105 ms avant de s'exécuter
Afin d'éviter que la phase d'interrogation ne bloque la boucle d'événements, libuv (la bibliothèque en langage C qui implémente la boucle d'événements et tous les comportements asynchrones sur la plateforme ) fait la même chose dans la phase d'interrogation. Il y a également un arrêt maximum pour interroger plus d'événements
Cette phase exécute des rappels pour certaines opérations système (telles que les types d'erreur TCP). Par exemple, certains systèmes * nix souhaitent attendre qu'une erreur soit signalée si un socket TCP reçoit ECONNREFUSED lors de la tentative de connexion. Celui-ci sera mis en file d'attente pour exécution pendant la phase de rappel en attente.
La phase d'interrogation a deux fonctions principales
Lorsque la boucle d'événements entre dans la phase d'interrogation et qu'il n'y a pas de minuterie, ce qui suit se produit Deux choses
Une fois la file d'attente d'interrogation vide Oui, la boucle d'événements détectera si le minuteur a expiré. Si tel est le cas, la boucle d'événements atteindra l'étape des minuteurs et exécutera le rappel du minuteur
.此阶段允许人们在 poll 阶段完成后立即执行回调。 如果轮询阶段变得空闲并且脚本已使用 setImmediate() 排队,则事件循环可能会继续到 check 阶段而不是等待。
setImmediate() 实际上是一个特殊的计时器,它在事件循环的单独阶段运行。 它使用一个 libuv API 来安排在 poll 阶段完成后执行的回调。
通常,随着代码的执行,事件循环最终会到达 poll 阶段,它将等待传入的连接、请求等。但是,如果使用 setImmediate() 安排了回调并且 poll 阶段变得空闲,它将结束并继续 check 阶段,而不是等待 poll 事件。
如果一个 socket 或者操作突然被关闭(e.g socket.destroy()),close 事件会被发送到这个阶段,否则会通过process.nextTick()发送
setImmediate() 和 setTimeout() 是相似的,但是不同的行为取决于在什么时候被调用
每个回调执行的顺序依赖他们被调用的上下本环境,如果在同一个模块被同时调用,那么时间会受到进程性能的限制(这也会被运行在这台机器的其他应用所影响)
例如,如果我们不在I/O里边运行下面的脚本,尽管它受进程性能的影响,但是不能够确定这两个计时器的执行顺序:
// timeout_vs_immediate.js setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
$ node timeout_vs_immediate.js timeout immediate $ node timeout_vs_immediate.js immediate timeout
然而,如果你移动到I/O 循环中,immediate 回调总是会先执行
// timeout_vs_immediate.js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); }); });
$ node timeout_vs_immediate.js immediate timeout $ node timeout_vs_immediate.js immediate timeout
setImmediate 相对于 setTimeout 的优势是 setImmediate 如果在I/O 中总是会优先于任何计时器被先执行,与存在多少计时器无关。
尽管 process.nextTick() 是异步API的一部分,但是你可能已经注意到了它没有出现在图表中,这是因为 process.nextTick() 不是事件循环技术的一部分,相反,当前操作执行完毕之后 nextTickQueue 会被执行,无论事件循环的当前阶段如何。 在这里,操作被定义为来自底层 C/C++ 处理程序的转换,并处理需要执行的 JavaScript。 根据图表,你可以在任意阶段调用 process.nextTick(),在事件循环继续执行之前,所有传递给 process.nextTick() 的回调都将被执行,这个会导致一些坏的情况因为它允许你递归调用 process.nextTick() "starve" 你的 I/O ,这会阻止事件循环进入 poll 阶段。
为什么这种情况会被包含在Node.js中?因为Node.js的设计理念是一个API应该总是异步的即使它不必须,看看下面的片段
function apiCall(arg, callback) { if (typeof arg !== 'string') return process.nextTick( callback, new TypeError('argument should be string') ); }
该片段会进行参数检查,如果不正确,它会将错误传递给回调。 API 最近更新,允许将参数传递给 process.nextTick() 允许它接受在回调之后传递的任何参数作为回调的参数传播,因此您不必嵌套函数。
我们正在做的是将错误传回给用户,但前提是我们允许用户的其余代码执行。 通过使用 process.nextTick(),我们保证 apiCall() 总是在用户代码的其余部分之后和允许事件循环继续之前运行它的回调。 为了实现这一点,允许 JS 调用堆栈展开,然后立即执行提供的回调,这允许人们对 process.nextTick() 进行递归调用,而不会达到 RangeError:从 v8 开始超出最大调用堆栈大小。
更多node相关知识,请访问:nodejs 教程!
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!