Maison  >  Article  >  interface Web  >  Une analyse approfondie des événements et des boucles d'événements dans nodejs

Une analyse approfondie des événements et des boucles d'événements dans nodejs

青灯夜游
青灯夜游avant
2021-04-14 11:06:122163parcourir

Cet article vous expliquera les événements dans nodejs et discutera des différences entre setTimeout, setImmediate et process.nextTick. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.

Une analyse approfondie des événements et des boucles d'événements dans nodejs

Boucle d'événements dans nodejs


Bien que nodejs soit monothread, nodejs peut fonctionner en le déléguant à le noyau système, qui traite ces tâches en arrière-plan, lorsque la tâche est terminée, il en informe nodejs, déclenchant ainsi la méthode de rappel dans nodejs.

Ces rappels seront ajoutés à la file d'attente du round robin et éventuellement exécutés.

Avec cette conception de boucle d'événements, nodejs peut enfin réaliser des E/S non bloquantes. [Recommandations associées : "Tutoriel nodejs"]

La boucle d'événements dans nodejs est divisée en phases La figure suivante répertorie l'ordre d'exécution de chaque phase :

Chaque phase maintient une file d'attente de rappel, qui est une file d'attente FIFO.

Lors de l'entrée dans une phase, il exécutera d'abord les tâches de la phase, puis exécutera les tâches de rappel appartenant à la phase.

Lorsque toutes les tâches de cette file d'attente de rappel auront été exécutées ou que le nombre maximum d'exécutions de rappel aura été atteint, la phase suivante entrera.

Notez que les implémentations spécifiques de Windows et Linux sont légèrement différentes. Ici, nous nous concentrons uniquement sur les phases les plus importantes.

Question : Lors de l'exécution de la phase, pourquoi est-il nécessaire de limiter le nombre maximum d'exécutions de callback ?

Réponse : Dans des cas extrêmes, une certaine phase peut nécessiter l'exécution d'un grand nombre de rappels. Si l'exécution de ces rappels prend trop de temps, cela bloquera le fonctionnement de nodejs, nous définissons donc le nombre de rappels. Limiter les exécutions de rappel pour éviter les blocages à long terme de nodejs.

Explication détaillée de la phase


Dans l'image ci-dessus, nous avons répertorié 6 phases, et nous les expliquerons une par une ensuite.


minuteries

La signification chinoise des minuteries est minuterie, ce qui signifie exécuter une certaine fonction de rappel à une heure ou un intervalle donné.

Il existe deux fonctions de minuterie courantes : setTimeout et setInterval.

De manière générale, ces fonctions de rappel seront exécutées autant que possible après l'expiration, mais seront affectées par l'exécution d'autres rappels. Regardons un exemple :

const fs = require(&#39;fs&#39;);function someAsyncOperation(callback) {  // Assume this takes 95ms to complete  fs.readFile(&#39;/path/to/file&#39;, 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 completesomeAsyncOperation(() => {  const startCallback = Date.now();  // do something that will take 10ms...  while (Date.now() - startCallback < 10) {    // do nothing  }});

Dans l'exemple ci-dessus, nous avons appelé someAsyncOperation. Cette fonction revient d'abord pour exécuter la méthode readFile. Supposons que cette méthode prenne 95 ms. Exécutez ensuite la fonction de rappel de readFile. Ce rappel s'exécutera pendant 10 ms. Enfin, revenez en arrière et exécutez le rappel dans setTimeout.

Ainsi, dans l'exemple ci-dessus, bien que setTimeout soit spécifié pour s'exécuter après 100 ms, il doit en fait attendre 95 + 10 = 105 ms avant d'être réellement exécuté.

rappels en attente

Cette phase effectuera certaines opérations de rappel du système. Par exemple, lors de l'établissement d'une connexion TCP, le socket TCP reçoit le signal ECONNREFUSED dans certains Linux. l'erreur sera signalée dans le système d'exploitation et le rappel de ce système sera exécuté dans les rappels en attente.

Ou l'opération de rappel d'E/S qui doit être effectuée dans la prochaine boucle d'événement.

inactif, préparer

inactif, préparer est une phase interne, je ne la présenterai donc pas en détail ici.

interrogation d'interrogation

le sondage détectera les nouveaux événements d'E/S et exécutera les rappels liés aux E/S. Notez que les rappels ici font référence à autre chose que Désactiver presque tous les rappels. événements à l'exception du rappel, des minuteries et de setImmediate.

poll gère principalement deux choses : interroger les E/S, calculer le temps de blocage, puis traiter les événements dans la file d'attente d'interrogation.

Si la file d'attente d'interrogation n'est pas vide, la boucle d'événements parcourra les rappels dans la file d'attente, puis les exécutera de manière synchrone un par un jusqu'à ce que la file d'attente soit consommée ou que la limite du nombre de rappels soit atteinte.

Étant donné que les rappels dans la file d'attente sont exécutés de manière synchrone un par un, un blocage peut se produire.

Si la file d'attente d'interrogation est vide et que setImmediate est appelé dans le code, elle passera immédiatement à la phase de vérification suivante, puis exécutera le rappel dans setImmediate. Si setImmediate n'est pas appelé, il continuera à attendre que le nouveau rappel soit ajouté à la file d'attente et exécuté.

check

est principalement utilisé pour exécuter le rappel de setImmediate.

setImmediate peut être considéré comme un minuteur unique fonctionnant dans une phase distincte, et l'API libuv sous-jacente est utilisée pour planifier les rappels.

De manière générale, s'il y a un rappel appelé setImmediate dans la phase d'interrogation, la phase d'interrogation se terminera immédiatement lorsque la file d'attente d'interrogation est vide, et la phase de vérification sera entrée pour exécuter la méthode de rappel correspondante.

rappels de fermeture

La dernière phase consiste à gérer les rappels dans l'événement de fermeture. Par exemple, si un socket est soudainement fermé, un événement close sera déclenché et le rappel associé sera appelé.

setTimeout 和 setImmediate的区别


setTimeout和setImmediate有什么不同呢?

从上图的phase阶段可以看出,setTimeout中的callback是在timer phase中执行的,而setImmediate是在check阶段执行的。

从语义上讲,setTimeout指的是,在给定的时间之后运行某个callback。而setImmediate是在执行完当前loop中的 I/O操作之后,立马执行。

那么这两个方法的执行顺序上有什么区别呢?

下面我们举两个例子,第一个例子中两个方法都是在主模块中运行:

setTimeout(() => {
  console.log('timeout');
}, 0);

setImmediate(() => {
  console.log('immediate');
});

这样运行两个方法的执行顺序是不确定,因为可能受到其他执行程序的影响。

第二个例子是在I/O模块中运行这两个方法:

const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
});

你会发现,在I/O模块中,setImmediate一定会在setTimeout之前执行。

两者的共同点

setTimeout和setImmediate两者都有一个返回值,我们可以通过这个返回值,来对timer进行clear操作:

const timeoutObj = setTimeout(() => {
  console.log(&#39;timeout beyond time&#39;);
}, 1500);

const immediateObj = setImmediate(() => {
  console.log(&#39;immediately executing immediate&#39;);
});

const intervalObj = setInterval(() => {
  console.log(&#39;interviewing the interval&#39;);
}, 500);

clearTimeout(timeoutObj);
clearImmediate(immediateObj);
clearInterval(intervalObj);

clear操作也可以clear intervalObj。

unref 和 ref

setTimeout和setInterval返回的对象都是Timeout对象。

如果这个timeout对象是最后要执行的timeout对象,那么可以使用unref方法来取消其执行,取消执行完毕,可以使用ref来恢复它的执行。

const timerObj = setTimeout(() => {
  console.log(&#39;will i run?&#39;);
});

timerObj.unref();

setImmediate(() => {
  timerObj.ref();
});

注意,如果有多个timeout对象,只有最后一个timeout对象的unref方法才会生效。

process.nextTick


process.nextTick也是一种异步API,但是它和timer是不同的。

如果我们在一个phase中调用process.nextTick,那么nextTick中的callback会在这个phase完成,进入event loop的下一个phase之前完成。

这样做就会有一个问题,如果我们在process.nextTick中进行递归调用的话,这个phase将会被阻塞,影响event loop的正常执行。

那么,为什么我们还会有process.nextTick呢?

考虑下面的一个例子:

let bar;

function someAsyncApiCall(callback) { callback(); }

someAsyncApiCall(() => {
  console.log(&#39;bar&#39;, bar); // undefined
});

bar = 1;

上面的例子中,我们定义了一个someAsyncApiCall方法,里面执行了传入的callback函数。

这个callback函数想要输出bar的值,但是bar的值是在someAsyncApiCall方法之后被赋值的。

这个例子最终会导致输出的bar值是undefined。

我们的本意是想让用户程序执行完毕之后,再调用callback,那么我们可以使用process.nextTick来对上面的例子进行改写:

let bar;

function someAsyncApiCall(callback) {
  process.nextTick(callback);
}

someAsyncApiCall(() => {
  console.log(&#39;bar&#39;, bar); // 1
});

bar = 1;

我们再看一个实际中使用的例子:

const server = net.createServer(() => {}).listen(8080);

server.on(&#39;listening&#39;, () => {});

上面的例子是最简单的nodejs创建web服务。

上面的例子有什么问题呢?listen(8000) 方法将会立马绑定8000端口。但是这个时候,server的listening事件绑定代码还没有执行。

这里实际上就用到了process.nextTick技术,从而不管我们在什么地方绑定listening事件,都可以监听到listen事件。

process.nextTick 和 setImmediate 的区别

process.nextTick 是立马在当前phase执行callback,而setImmediate是在check阶段执行callback。

所以process.nextTick要比setImmediate的执行顺序优先。

实际上,process.nextTick和setImmediate的语义应该进行互换。因为process.nextTick表示的才是immediate,而setImmediate表示的是next tick。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/nodejs-event-more/

本文来源:flydean的博客

更多编程相关知识,请访问:编程视频!!

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