MST

星途 面试题库

面试题:网络编程之Node.js非阻塞I/O的事件循环机制

深入解释Node.js非阻塞I/O操作背后的事件循环机制,包括事件循环的各个阶段及其作用,并且说明如果在事件循环中出现一个长时间运行的同步任务,会对非阻塞I/O操作产生什么影响以及如何解决。
34.2万 热度难度
后端开发网络编程

知识考点

AI 面试

面试题答案

一键面试

事件循环机制概述

Node.js 采用事件驱动、非阻塞 I/O 模型,事件循环是实现这一模型的核心机制。它持续检查事件队列,依次处理队列中的事件。

事件循环的各个阶段及其作用

  1. timers(定时器阶段)
    • 作用:这个阶段执行已经安排好的 setTimeout()setInterval() 回调函数。它会检查定时器队列中到期的定时器,将其回调函数加入到这个阶段的执行队列中执行。
  2. pending callbacks(待定回调阶段)
    • 作用:执行系统底层的一些回调,例如 TCP 连接错误等底层操作的回调。这些回调是由操作系统或 Node.js 内部模块在合适的时候放入队列中的。
  3. idle, prepare(闲置、准备阶段)
    • 作用:仅在内部使用,Node.js 内部在此阶段做一些准备工作,一般开发者很少直接与之交互。
  4. poll(轮询阶段)
    • 作用:这是事件循环中非常重要的一个阶段。在这个阶段,事件循环会检查是否有新的 I/O 事件,如果有则执行它们的回调函数。同时,如果没有定时器到期,事件循环会在这里等待新的 I/O 事件。如果有已到期的定时器,事件循环会结束轮询阶段,进入到 timers 阶段执行定时器回调。
  5. check(检查阶段)
    • 作用:此阶段专门执行 setImmediate() 安排的回调函数。setImmediate() 的回调会被加入到这个阶段的队列中执行,它与 setTimeout() 不同,setImmediate() 总是在 poll 阶段之后,timers 阶段之前执行(如果当前事件循环中 poll 阶段没有 I/O 事件需要处理且没有定时器到期)。
  6. close callbacks(关闭回调阶段)
    • 作用:执行一些关闭相关的回调,例如 socket.on('close', ...) 的回调函数,当一个 socket 或 handle 被关闭时,相应的回调会在这个阶段执行。

长时间运行的同步任务的影响

如果在事件循环中出现一个长时间运行的同步任务,会阻塞事件循环。因为事件循环是单线程的,在同步任务执行期间,其他任务(包括非阻塞 I/O 操作的回调)都无法执行。这意味着新的 I/O 事件即使发生了,其回调也会被阻塞在队列中,无法及时得到处理,导致应用程序看起来像是“卡死”,响应变得迟缓。

解决方法

  1. 使用异步函数和回调:将同步任务改写成异步任务,例如使用 fs.readFile() 代替 fs.readFileSync() 来读取文件,这样 I/O 操作就不会阻塞事件循环。
  2. Web Workers(浏览器环境类似概念):虽然 Node.js 没有完全等同于浏览器 Web Workers 的概念,但可以使用 child_process 模块创建子进程来处理计算密集型任务。子进程在独立的线程或进程中运行,不会阻塞主线程的事件循环。
  3. 使用队列和任务调度:可以创建一个任务队列,将长时间运行的任务分割成小块,每次从队列中取出一块任务执行一小段时间,然后让出事件循环,让其他任务有机会执行。例如,可以使用 setImmediate()process.nextTick() 来调度这些小块任务的执行。