You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js多I/O调用场景下libuv线程的行为疑问

Node.js多I/O调用场景下libuv线程的行为疑问

你观察到的这个现象完全是预期行为,咱们来拆解一下libuv在这里的工作逻辑:

  • libuv的I/O线程并非“独占式”处理单个文件
    libuv采用的是异步I/O模型,当线程发起一个文件读取请求后,不会阻塞等待系统返回完整文件内容,而是立刻转去处理下一个待办的I/O任务。只有当内核准备好某段文件数据后,才会通知libuv线程,触发对应流的data事件;处理完这个事件后,线程又会回去处理其他等待中的I/O任务。这就是为什么你会看到多个流的data事件交替出现——线程是在多个未完成的I/O请求之间动态调度,而非等一个文件全读完才处理下一个。

  • 默认4个线程的作用是提升并行处理能力
    这里的4个线程是用来并行发起和处理多个异步I/O请求的,但每个线程本身也遵循事件驱动的调度逻辑。哪怕只有一个线程,也能同时处理多个异步文件读取(因为发起请求后就释放了线程资源),4个线程只是让更多I/O请求能同时被内核处理,从而提升整体的I/O吞吐量。

  • 结合你的代码来看
    你一次性创建了多个文件读取流,这些流会立刻向libuv的I/O线程池提交读取请求。线程池中的线程会逐个发起异步读操作,之后就转去处理下一个请求;当内核返回某文件的一块数据时,对应的data事件就会触发,所以你会看到不同流的data日志交替打印,而非前4个流全部读完后再处理后面的文件。

如果你想要实现“读完一个文件再处理下一个”的效果,可以用同步读取方式,或者通过async/await手动控制异步流的串行执行,但这样会牺牲异步I/O的吞吐量优势。

你的测试代码

const fs = require('node:fs');
const path = require('node:path');

function getFilePaths(directory){
  let files = fs.readdirSync(directory)
  return files
  .map((file) => path.join(directory, file))
  .filter((filePath) => fs.statSync(filePath).isFile())
}

const directory = 'D:\\test-folder';

const files = getFilePaths(directory)

const streams = []

files.forEach((file, index)=>{
  streams.push([index, fs.createReadStream(file)])
})

streams.forEach(([index, stream])=>{
  stream.on('data', () => {
    console.log(`Data: Stream ${index}`);
  });

  stream.on('end', () => {
    console.log(`End: Stream ${index}`);
  });

  stream.on('error', (err) => {
    console.error(`Error: Stream ${index}`);
  });

})

实际输出示例

Console output showing data events from various streams

备注:内容来源于stack exchange,提问作者ariko stephen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 14:59:29