Nodejs 在底层是如何进行 I/O 的?

aka*_*kar 4 javascript io concurrency operating-system node.js

我知道nodejs使用libuv提供的线程池来执行并发作业。我读到 NodeJS 只需要其中一个线程来完成所有 I/O 绑定活动

那么非阻塞文件操作是如何发生的呢?

如果我迭代一个包含 100 个文件的文件夹并使用 readFile() 打开所有文件,那么在节点、磁盘和操作系统之间实际上会发生什么?

如果在其他多线程和阻塞语言中执行相同的操作将导致线程等待 i/o 完成,那么谁在 nodejs 中等待 i/o 完成呢?,特别是当多个文件操作同时发生时?

谢谢 !

jfr*_*d00 6

Nodejs中的文件IO使用线程池。默认情况下,池中有 4 个线程。

当某些 Javascript 调用文件操作(例如fs.open()在 Node.js 中)时,该调用将被路由到 Node.js 实现中的 libuv 库中的某些本机代码。该代码将特定的文件操作添加到等待操作系统线程的项目队列中。如果操作系统线程立即可用,则该操作系统线程将被赋予执行文件操作的任务,然后原始 libuv 函数返回,该函数将返回到原始 Javascript,并且 nodejs 继续运行其他 Javascript。

然后,过了一段时间,线程完成文件操作并得到结果。它将一个事件插入到nodejs事件队列中,其中包含完成的结果以及随原始文件操作传递的原始回调。

当nodejs 完成执行它正在运行的任何其他Javascript 时,它会从事件队列中提取下一个事件,并调用与该事件关联的Javascript 回调,并向其传递与该事件关联的数据。这会触发最初传递给fs.open()调用的 Javascript 回调。

如果在上面的第二步中,线程池中没有可用线程,则该任务将被添加到队列中,libuv 立即将控制权返回给 nodejs,以便它可以运行其他 Javascript。稍后,当池中的一个现有线程完成并完成将事件添加到事件队列的工作时,它将从线程池队列中拉出下一个项目,给它现在可用的线程并让它开始运行其操作。

通过这种方式,从 Javascript 方面来看,所有文件操作似乎都是非阻塞和异步的。如果线程池中有可用线程,它们要么立即启动,要么坐在线程池队列中,直到有可用线程为止。这部分对于 Javascript 方面来说是不可见的。无论哪种方式,操作都会被启动或排队,控制权会立即返回到nodejs以继续运行其他Javascript。而且,无论哪种方式,操作都会稍后完成,并且通过 Javascript 事件队列调用与文件操作关联的 Javascript 回调。这提供了 Nodejs 用于所有文件 I/O 的异步、非阻塞、事件驱动机制。

  • 最多 4 个文件操作可以在单独的操作系统线程中同时运行。如果nodejs尝试运行超过4个,那么其他的将排队,直到现有的4个之一完成。从 Nodejs Javascript 的角度来看,所有请求都是非阻塞和异步的 - 只是有些请求可能会在队列中等待一段时间才能真正开始运行。请注意,对于旋转磁盘,这种相同类型的队列也会发生在磁盘控制器级别,因为磁盘读/写磁头一次只能放置在一个磁道上。 (2认同)