设为首页 加入收藏

TOP

【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick(一)
2019-09-17 18:48:42 】 浏览:59
Tags:nodejs 原理 源码 赏析 Node.js 事件 循环 定时器 process.nextTick

示例代码托管在:http://www.github.com/dashnowords/blogs

博客园地址:《大史住在大前端》原创博文目录

华为云社区地址:【你要的前端打怪升级指南】

原文地址:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick

如果你常年游走于Nodejs中文网,可能已经错过了官方网站上的第一手资料,Nodejs中文网并没有翻译这些非常高质量的核心文章,只提供了中文版的API文档(已经很不容易了,没有任何黑它的意思,我也是中文网的受益者),它们涵盖了Node.js中从核心概念到相关工具等等非常重要的知识,下面是博文的目录,你知道该怎么做了。

Event Loop 是什么?

事件循环是Node.js能够实现非阻塞I/O的基础,尽管java script应用是单线程运行的,但是它可以将操作向下传递到系统内核去执行。

大多数现代系统内核都是支持多线程的,它们可以同时在后台处理多个操作。当其中任何一个任务完成后,内核会通知Node.js,这样它就可以把对应的回调函数添加进poll队列,回调函数最终就能够被执行,后文中我们还会进行更详细的解释。

Event Loop 基本解释

Node.js开始运行时,它就会初始化Event Loop,然后处理脚本文件(或者在REPLread-eva l-print-loop)环境中执行,本文不做深入探讨)中的异步API调用,定时器,或process.nextTick方法调用,然后就会开始处理事件循环(Event Loop)。

下图展示了事件循环的各个阶段(每一个盒子被称为事件循环中一个“阶段”):

每一个阶段都维护了一个先进先出的待执行回调函数队列,尽管每一个阶段都有自己独特的处理方式,但总体来说,当事件循环进入一个具体的阶段时,它将处理与这个阶段有关的所有操作,然后执行这个阶段对应队列中的回调函数直到队列为空,或者达到了该阶段允许运行函数的数量的最大值,当满足任何一个条件时,事件循环都会进入下一个阶段,以此类推。

因为任何阶段相关的操作都可能导致更多的待执行操作产生,而新事件会被内核添加进poll队列中,当poll队列中的回调函数被执行时允许继续向当前阶段的poll队列中添加新的回调函数,于是长时间运行的回调函数可能就会导致事件循环在poll阶段停留时间过长,你可以在后文的timerspoll章节查看更多的内容。

提示:Windows和Unix/Linux在实现上有细小的差别,但并不影响本文的演示,不同的系统可能会存在7-8个阶段,但是最终要的阶段上图中已经展示了,这些是Node.js实际会使用到的。

事件循环阶段概览

  • timers-本阶段执行通过setTimeout( )setInterval( )添加的已经到时的计划任务
  • pending callbacks-将一些I/O回调函数延迟到下一循环执行(这里不是很确定)
  • idle,prepare-内部使用的阶段
  • poll-检查新的I/O事件;执行相关I/O的回调(除了“close回调”,“定时器回调”和setImmediate( )添加的回调外几乎所有其他回调函数);node有可能会在这里产生阻塞
  • check-执行setImmediate( )添加的回调函数
  • close callbacks-用于关闭功能的回调函数,例如socket.on('close',......)

在每轮事件周期之间,Node.js会检查是否有处于等待中的异步I/O或定时器,如果没有的话就会关闭当前程序。

事件循环细节

timers

一个timer会明确一个时间点,回调函数会在时间超过这个时间点后被执行,而不是开发者希望的精确时间。一旦定时器时间过期,回调函数就会尽可能早地被调度执行,然而操作系统的调度方式和其他的回调函数都有可能会导致某个定时器回调函数被延迟。

提示:技术上来说,poll阶段控制着timers如何被执行。

下面的示例中,你使用了一个100ms后过期的定时器,接着花费了95ms使用异步文件读取API异步读取了某个文件:

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
  }
});

当事件循环进入poll阶段时,它的待执行队列是空的(fs.readFile( )还没有完成),所以它将等待一定时间(当前时间距离最快到期的定时器到期时间之间的差值)。95ms过去后,fs.readFile( )完成了文件读取,并花费了10ms将回调函数添加进poll的执行队列是它被执行。当回调函数执行完毕后,队列中没有更多的回调函数了,事件循环就会再次检查下一个待触发的timer是否已经到期,如果是,则事件循环就会绕回timers阶段去执行到期timer的回调函数。在这个示例中,你会看到timer从设置定时器到回调函数被触发一共花费了105ms.

注意:为了避免在poll阶段阻塞事件循环,libuv(Node.js底层用于实现事件循环和异步特性的C语言库)设置了一个硬上限值(该值会根据系统不同而有变化),使得poll阶段只能将有限数量的回调函数添加进poll队列。

pending callbacks

这个阶段会执行一些系统操作的回调函数,例如一些TCP的错误。比如一个TCP的socket对象尝试连接另一个socket时收到了ECONNREFUSED,一些Linux系统会希望汇报这类错误,这类回调函数就会被添加在pending callbacks阶段的待执行队列中。

p

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇百度地图API图标、文本、图例与连.. 下一篇codemirror 行高 字体 行间距 设置

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目