当前位置:网站首页 > Node.js开发 > 正文

nodejs事件循环与多进程(二)——fs和setTimeout、setImmediate关系&事件循环是异步操作&process.nextTick阻塞IO操作&app.on订阅、app.emit触发

nodejs事件循环与多进程(二)——fs和setTimeout、setImmediate关系&事件循环是异步操作&process.nextTick阻塞IO操作&app.on订阅、app.emit触发

第三章 nodejs事件循环

当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下六个循环阶段,nodejs事件循环和浏览器的事件循环完全不一样。

When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop.

在这里插入图片描述

注意: 图中的每个方框被称作事件循环的一个”阶段(phase)”, 这6个阶段为一轮事件循环。

阶段概览

  • timers(定时器) : 此阶段执行那些由 setTimeout()setInterval() 调度的回调函数.
  • I/O callbacks(I/O回调) : 此阶段会执行几乎所有的回调函数, 除了 close callbacks(关闭回调) 和 那些由 timerssetImmediate()调度的回调.

    setImmediate 约等于 setTimeout(cb,0)

  • idle(空转), prepare : 此阶段只在内部使用
  • poll(轮询) : 检索新的I/O事件; 在恰当的时候Node会阻塞在这个阶段
  • check(检查) : setImmediate() 设置的回调会在此阶段被调用
  • close callbacks(关闭事件的回调): 诸如 socket.on('close', ...) 此类的回调在此阶段被调用

在事件循环的每次运行之间, Node.js会检查它是否在等待异步I/O或定时器, 如果没有的话就会自动关闭.

如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:

  • 如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;
  • 如果poll queue为空,将会发生下面情况:
    • 如果代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
    • 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue,一旦到达就立即执行

如果event loop进入了 poll阶段,且代码设定了timer:

  • 如果poll queue进入空状态时(即poll 阶段为空闲状态),event loop将检查timers,如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入 timers 阶段,并执行timer queue.

代码执行1

path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径。

fs.readFile 异步地读取文件的全部内容。

__dirname 总是指向被执行文件夹的绝对路径

示意图

在这里插入图片描述

代码

demo.js

var fs = require('fs'); var path = require('path'); function someAsyncOperation (callback) { 
    // 花费2毫秒 fs.readFile(path.resolve(__dirname, '/read.txt'), callback); } var timeoutScheduled = Date.now(); var fileReadTime = 0; setTimeout(function () { 
    var delay = Date.now() - timeoutScheduled; console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled"); console.log('fileReaderTime',fileReadtime - timeoutScheduled); }, 10); someAsyncOperation(function () { 
    fileReadtime = Date.now(); while(Date.now() - fileReadtime < 20) { 
    // 卡住20ms } }); // 关注: setTimeout它的回调执行时机, fs.readFile 回调的执行时间 // 事件循环调度是异步操作,重点关注异步代码的执行时机 

打开命令终端

node ./demo.js 

打印显示

代码执行2

测试运行文件时间示例

readfile.js

var fs = require('fs') var path = require('path') console.time('file') fs.readFile(path.resolve(__dirname,'./同目录测试文件.txt'),'utf8',()=>{ 
    console.timeEnd('file') }) 

注:__dirname是两个下划线

打开终端

node ./readfile.js 

显示

file:11.468ms

实例
var fs = require('fs'); function someAsyncOperation (callback) { 
    var time = Date.now(); // 花费9毫秒 fs.readFile('/path/to/xxxx.pdf', callback); } var timeoutScheduled = Date.now(); var fileReadTime = 0; var delay = 0; setTimeout(function () { 
    delay = Date.now() - timeoutScheduled; }, 5); someAsyncOperation(function () { 
    fileReadtime = Date.now(); while(Date.now() - fileReadtime < 20) { 
    } console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled"); console.log('fileReaderTime',fileReadtime - timeoutScheduled); }); // 关注: setTimeout它的回调执行时机, fs.readFile 回调的执行时间 // 事件循环调度是异步操作,重点关注异步代码的执行时机 

实例

var fs = require('fs'); var path = require('path'); function someAsyncOperation (callback) { 
    // 花费9毫秒 fs.readFile(path.resolve(__dirname, '/read.txt'), callback); } var timeoutScheduled = Date.now(); var fileReadTime = 0; setTimeout(function () { 
    var delay = Date.now() - timeoutScheduled; console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled"); console.log('fileReaderTime',fileReadtime - timeoutScheduled); }, 5); someAsyncOperation(function () { 
    fileReadtime = Date.now(); while(Date.now() - fileReadtime < 20) { 
    // 卡住20ms } }); // 关注: setTimeout它的回调执行时机, fs.readFile 回调的执行时间 // 事件循环调度是异步操作,重点关注异步代码的执行时机 

代码执行3

在nodejs中, setTimeout(demo, 0) === setTimeout(demo, 1)

在浏览器里面 setTimeout(demo, 0) === setTimeout(demo, 4)

setTimeout(function timeout () { 
    console.log('timeout'); },1); setImmediate(function immediate () { 
    console.log('immediate'); }); // 执行顺序是不确定的,setImmediate它有时候是1ms之前执行,有时候又是1ms之后执行? 

因为event loop的启动也是需要时间的,可能执行到poll阶段已经超过了1ms,此时setTimeout会先执行。反之setImmediate先执行

var path = require('path'); var fs = require('fs'); fs.readFile(path.resolve(__dirname, '/read.txt'), () => { 
    setImmediate(() => { 
    console.log('setImmediate'); }) setTimeout(() => { 
    console.log('setTimeout') }, 0) }); // 执行顺序是确定的 setImmediate在前,setTimeout在后 

process.nextTick

process.nextTick()不在event loop的任何阶段执行,而是在各个阶段切换的中间执行,即从一个阶段切换到下个阶段前执行。

命令终端中

node 文件名

var fs = require('fs'); fs.readFile(__filename, () => { 
    setTimeout(() => { 
    console.log('setTimeout'); }, 0); setImmediate(() => { 
    console.log('setImmediate'); process.nextTick(()=>{ 
    console.log('nextTick3'); }) }); process.nextTick(()=>{ 
    console.log('nextTick1'); }) process.nextTick(()=>{ 
    console.log('nextTick2'); }) }); // 执行顺序是 —— nextTick1、nextTick2、setImmediate、nextTick3、setTimeout 

实例

var fs = require('fs') var path = require('path') fs.readFile(path.resolve(__dirname, '/read.txt'), () => { 
    setTimeout(() => { 
    console.log('setTimeout') }, 0) setImmediate(() => { 
    console.log('setImmediate') process.nextTick(() => { 
    console.log('nextTick3') }) }) process.nextTick(() => { 
    console.log('nextTick1') }) process.nextTick(() => { 
    console.log('nextTick2') }) }) // 执行顺序是 —— nextTick1、nextTick2、setImmediate、nextTick3、setTimeout 
设计原因

允许开发者通过递归调用 process.nextTick() 来阻塞I/O操作。

nextTick应用场景
  1. 在多个事件里交叉执行CPU运算密集型的任务:
var http = require('http'); function compute() { 
    process.nextTick(compute);// } http.createServer(function(req, res) { 
    // 服务http请求的时候,还能抽空进行一些计算任务 res.writeHead(200, { 
   'Content-Type': 'text/plain'}); res.end('Hello World'); }).listen(5000, '127.0.0.1'); compute(); 

在这种模式下,我们不需要递归的调用compute(),我们只需要在事件循环中使用process.nextTick()定义compute()在下一个时间点执行即可。在这个过程中,如果有新的http请求进来,事件循环机制会先处理新的请求,然后再调用compute()。反之,如果你把compute()放在一个递归调用里,那系统就会一直阻塞在compute()里,无法处理新的http请求了。

  1. 保持回调函数异步执行的原则

当你给一个函数定义一个回调函数时,你要确保这个回调是被异步执行的。下面我们看一个例子,例子中的回调违反了这一原则:

function asyncFake(data, callback) { 
    if(data === 'foo') callback(true); else callback(false); } asyncFake('bar', function(result) { 
    // this callback is actually called synchronously! }); 

为什么这样不好呢?我们来看Node.js 文档里一段代码:

var client = net.connect(8124, function() { 
    console.log('client connected'); client.write('world!\r\n'); }); 

在上面的代码里,如果因为某种原因,net.connect()变成同步执行的了,回调函数就会被立刻执行,因此回调函数写到客户端的变量就永远不会被初始化了。

这种情况下我们就可以使用process.nextTick()把上面asyncFake()改成异步执行的:

function asyncReal(data, callback) { 
    process.nextTick(function() { 
    callback(data === 'foo'); }); } 
  1. 用在事件触发过程中

    EventEmitter有2个比较核心的方法, on和emit。node自带发布/订阅模式

var EventEmitter = require('events').EventEmitter; function StreamLibrary(resourceName) { 
    this.emit('start'); } StreamLibrary.prototype.__proto__ = EventEmitter.prototype; // inherit from EventEmitter 
var stream = new StreamLibrary('fooResource'); stream.on('start', function() { 
    console.log('Reading has started'); }); 
function StreamLibrary(resourceName) { 
    var self = this; process.nextTick(function() { 
    self.emit('start'); }); // 保证订阅永远在发布之前 // read from the file, and for every chunk read, do:  } 

实例

event.js

var EventEmitter = require('events').EventEmitter; class App extends EventEmitter { 
    } var app = new App(); app.on('start', () => { 
    // on 订阅  console.log('start'); }); app.emit('start'); // emit 触发 console.log('1') // emit是同步的方法。 // 执行顺序是—— start、1 // 为什么是同步的?方便处理逻辑,比如:新增和删除 setTimeout , setImmedate, nextTick这三个是异步api 

event.js

var EventEmitter = require('events').EventEmitter; class App extends EventEmitter { 
    } var app = new App(); app.on('start', () => { 
    // on 订阅  console.log('start'); }); app.emit('start'); // emit 触发 console.log('1') // emit是同步的方法。 // 执行顺序是—— start、1 // 为什么是同步的?方便处理逻辑,比如:新增和删除 setTimeout , setImmedate, nextTick这三个是异步api 
到此这篇nodejs事件循环与多进程(二)——fs和setTimeout、setImmediate关系&事件循环是异步操作&process.nextTick阻塞IO操作&app.on订阅、app.emit触发的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • nodejs事件循环与多进程(四)——Process进程-Node全局对象&child_process子进程-exec、execSync、execFile、spawn、fork & Cluster集群2024-12-02 19:00:04
  • nodejs事件循环与多进程(五)——cluster多进程模型 & worker进程使用fork()函数,实现与master进程间通信 & 惊群之发生多线程多进程等待同一个socket事件2024-12-02 19:00:04
  • nodejs事件循环与多进程(六)——Nginx是HTTP和反向代理服务器& 正向代理-不知客户端需代理、反向代理-不知服务器需代理& cluster中的优雅退出和进程守护& IPC通信是进程间的通信2024-12-02 19:00:04
  • 将node_modules 文件夹中的所有包打包成压缩包代码实现2024-12-02 19:00:04
  • Baas接口标准(一)——GraphQL介绍、和RESTful对比 & GraphQL客户端与服务端交互(接口调用) & apollo-server框架开发之基于Node.js的GraphQL的开发2024-12-02 19:00:04
  • nodejs事件循环与多进程(一)——事件循环允许Node.js执行非阻塞IO操作 & js是操作DOM,决定了单线程 & 事件循环之宏任务setTimeout在后、微任务promise在前2024-12-02 19:00:04
  • Node.js网络通信(三)——构建http服务之创建http服务、根据url处理响应、响应html内容、处理页面中的静态资源、使用模版引擎& 构建https服务之原理、CA证书、搭建https服务器2024-12-02 19:00:04
  • Node.js 网络通信(二)02-构建UDP服务——UDP简介 & UDP三种传播方式-单播、广播、组播 & UDP一对多通信场景 & dgram模块用于构建UDP服务之Socket方法和事件2024-12-02 19:00:04
  • Node.js 网络通信(二)01-构建TCP服务——TCP全名为传输控制协议,属于传输层协议,如http协议 & 显著特征是在传输之前需要三次握手形成会话 & Socket通信模型2024-12-02 19:00:04
  • Node.js 网络通信(一)——网络通信相关概念、网络七层模型、mac 地址之ip地址、Port 端口号、域名 & TCP-传输控制协议、UDP-用户数据报协议、Socket套接字2024-12-02 19:00:04
  • 全屏图片