Quiet
  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我

bajiu

  • 主页
  • 归档
  • 分类
  • 标签
  • 链接
  • 关于我
Quiet主题
  • NodeJs
  • JavaScript

NodeJs之事件循环以及I/O多路复用

bajiu
前端

2024-06-13 19:30:00

Node.js 的事件循环允许我们在单线程中处理并发请求。事件循环的核心是将异步操作与回调函数结合起来,使得 Node.js 能够非阻塞地处理 I/O 操作。

工作原理

事件循环主要是分为几个阶段:

  1. Timers 阶段:处理所有定时器的回调,setTimeout 和 setInterval。每当 timers 阶段被调用时,Node.js 会检查是否有定时器到期,如果有,将执行相应的回调。
  2. I/O Callbacks 阶段:处理大部分的异步 I/O 操作的回调。此阶段处理的回调包括文件系统 I/O 操作、网络请求等。
  3. Idle, Prepare 阶段:这个阶段主要用于内部管理,通常开发者不需要关注。
  4. Poll 阶段:
    • 这是事件循环的核心阶段,主要负责等待 I/O 事件的发生。epoll 在这里发挥关键作用。
    • 在这个阶段,Node.js 会检查是否有 I/O 事件。如果有,它会将事件的回调放入待执行的队列中。如果没有事件发生,Node.js 会根据配置决定是继续等待还是转到下一个阶段。
  5. Check 阶段:处理 setImmediate 的回调。此阶段的回调在 Poll 阶段之后立即执行。
  6. Close Callbacks 阶段:处理关闭事件,例如关闭 socket 连接时的回调。

执行顺序

  • 在事件循环的每个循环中,首先执行 timers 阶段的回调,然后是 I/O callbacks,接着是 poll 阶段,如果有微任务(例如 Promise 的回调),它们会被立即执行。
  • 微任务的执行优先级高于宏任务。在同一循环中,所有微任务会在进入下一个阶段之前完成。

process.nextTick 是 Node.js 中一个重要的机制,用于在当前操作完成后立即执行回调函数。它与微任务队列密切相关:

  • process.nextTick 的回调函数会被放入一个特殊的队列中,它的执行优先级高于微任务队列(如 Promise 的 .then 回调)。
  • 这意味着在当前操作完成后,如果有 process.nextTick 的回调,它会在进入下一个事件循环阶段之前立即执行。
  • process.nextTick 通常用于确保某个操作在当前执行上下文中的下一个循环中执行。它可以用来避免阻塞主线程,并在当前代码执行完后立即处理一些操作,例如错误处理、状态更新等。

举个例子:

const fs = require('fs');

console.log('Start');

// 使用 process.nextTick
process.nextTick(() => {
  console.log('Next Tick 1');
});

// 使用 setTimeout
setTimeout(() => {
  console.log('Timeout 1');
}, 0);

// 文件读取(I/O 操作)
fs.readFile('example.txt', 'utf8', (err, data) => {
  console.log('File read complete');
});

// 使用 Promise
Promise.resolve().then(() => {
  console.log('Promise 1');
});

// 再次使用 process.nextTick
process.nextTick(() => {
  console.log('Next Tick 2');
});

// 结束日志
console.log('End');

答案:

Start
End
Next Tick 1
Next Tick 2
Promise 1
File read complete
Timeout 1

几种主要的 I/O 多路复用机制

  1. select

select 是最早的 I/O 多路复用机制之一,广泛用于 POSIX 系统。它允许程序监视多个文件描述符(如 socket、文件等),以便确定哪些文件描述符可读、可写或发生了错误。

工作原理:

select 接受三个主要参数,分别是可读、可写和异常文件描述符的集合。调用时,select 会阻塞,直到至少一个文件描述符变为可用。
一旦返回,程序需要遍历文件描述符集合,检查哪些文件描述符的状态发生了变化。

  1. poll

poll 是 select 的改进版本,解决了文件描述符数量限制的问题,支持的文件描述符数量几乎没有上限。

工作原理:

poll 使用一个数组来保存需要监视的文件描述符及其事件类型(可读、可写等)。与 select 不同,poll 在调用时不需要重设文件描述符集合。

  1. epoll
  • epoll 是 Linux 特有的 I/O 多路复用机制,设计用于处理大量并发连接,提供了比 select 和 poll 更高效的性能。

工作原理:

  • epoll 采用事件驱动的方式,使用内核的事件通知机制。程序只需将文件描述符添加到 epoll 实例中,然后通过调用 epoll_wait 等待事件发生。
  • 当文件描述符的状态变化时,内核会通知应用程序,不再需要不断轮询。
  1. IOCP (I/O Completion Ports)

I/O Completion Ports 是 Windows 平台上的高效 I/O 多路复用机制,专门为处理大量并发连接而设计。

工作原理:

使用线程池和 I/O 完成端口,允许线程从端口获取已完成的 I/O 操作。这种方法有效地将 I/O 操作与线程管理结合起来。

在 Windows 下,Node.js 使用的 I/O 多路复用机制主要是 I/O Completion Ports (IOCP)。它允许多个线程共享一个或多个 I/O 完成端口,提供了一种高效的方式来处理异步 I/O 操作。IOCP 结合了线程池的概念,可以自动管理和分配线程,以便在 I/O 操作完成时处理回调。这意味着应用程序可以更有效地利用系统资源,避免频繁的线程创建和销毁。

在 Node.js 中,底层的 I/O 操作(例如网络请求和文件操作)是通过 libuv 库实现的。libuv 是一个跨平台的异步 I/O 库,提供了对文件系统、网络和线程池的抽象。在 Windows 平台上,libuv 会使用 IOCP 来处理异步 I/O 操作。这意味着当你在 Node.js 中发起一个异步 I/O 请求时(比如使用 fs 模块读取文件或通过 HTTP 请求获取数据),libuv 会通过 IOCP 来管理这些操作的完成状态。

上一篇

electron中使用FFmpeg处理直播流

下一篇

electron中使用FFmpeg处理流媒体

©2024 By bajiu.