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}`); }); })
实际输出示例

备注:内容来源于stack exchange,提问作者ariko stephen
相关产品推荐
相关产品推荐

